malachite_float/conversion/string/
to_string.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::InnerFloat::{Finite, Infinity, NaN, Zero};
10use crate::alloc::string::ToString;
11use crate::{ComparableFloat, ComparableFloatRef, Float};
12use alloc::string::String;
13use core::fmt::{Debug, Display, Formatter, LowerHex, Result, Write};
14use malachite_base::num::arithmetic::traits::{
15    Abs, ModPowerOf2, RoundToMultipleOfPowerOf2, ShrRound,
16};
17use malachite_base::num::conversion::string::options::ToSciOptions;
18use malachite_base::num::conversion::traits::{ExactFrom, ToSci, WrappingFrom};
19use malachite_base::rounding_modes::RoundingMode::*;
20use malachite_q::Rational;
21
22fn replace_exponent_in_hex_string(s: &str, new_exponent: i32) -> String {
23    let exp_index = s.find('E').unwrap_or_else(|| panic!("{s}"));
24    let mut new_s = s[..exp_index].to_string();
25    if new_exponent > 0 {
26        write!(new_s, "E+{new_exponent}").unwrap();
27    } else {
28        write!(new_s, "E{new_exponent}").unwrap();
29    }
30    new_s
31}
32
33impl Display for Float {
34    fn fmt(&self, f: &mut Formatter) -> Result {
35        match self {
36            float_nan!() => write!(f, "NaN"),
37            float_infinity!() => write!(f, "Infinity"),
38            float_negative_infinity!() => write!(f, "-Infinity"),
39            float_zero!() => write!(f, "0.0"),
40            float_negative_zero!() => write!(f, "-0.0"),
41            _ => {
42                let exp = self.get_exponent().unwrap();
43                if exp.unsigned_abs() > 10000 {
44                    // The current slow implementation would take forever to try to convert a Float
45                    // with a very large or small exponent to a decimal string. Best to
46                    // short-circuit it for now.
47                    if *self < 0u32 {
48                        write!(f, "-")?;
49                    }
50                    return write!(f, "{}", if exp >= 0 { "too_big" } else { "too_small" });
51                }
52                let mut lower = self.clone();
53                let mut higher = self.clone();
54                lower.decrement();
55                higher.increment();
56                let self_q = Rational::exact_from(self);
57                let lower_q = Rational::exact_from(lower);
58                let higher_q = Rational::exact_from(higher);
59                let mut options = ToSciOptions::default();
60                for precision in 1.. {
61                    options.set_precision(precision);
62                    let s = self_q.to_sci_with_options(options).to_string();
63                    let s_lower = lower_q.to_sci_with_options(options).to_string();
64                    let s_higher = higher_q.to_sci_with_options(options).to_string();
65                    if s != s_lower && s != s_higher {
66                        return if s.contains('.') {
67                            write!(f, "{s}")
68                        } else if let Some(i) = s.find('e') {
69                            write!(f, "{}.0e{}", &s[..i], &s[i + 1..])
70                        } else {
71                            write!(f, "{s}.0")
72                        };
73                    }
74                }
75                panic!();
76            }
77        }
78    }
79}
80
81impl Debug for Float {
82    #[inline]
83    fn fmt(&self, f: &mut Formatter) -> Result {
84        Display::fmt(self, f)
85    }
86}
87
88impl LowerHex for Float {
89    #[inline]
90    fn fmt(&self, f: &mut Formatter) -> Result {
91        match self {
92            float_zero!() => f.write_str(if f.alternate() { "0x0.0" } else { "0.0" }),
93            float_negative_zero!() => f.write_str(if f.alternate() { "-0x0.0" } else { "-0.0" }),
94            Self(Finite {
95                exponent,
96                precision,
97                ..
98            }) => {
99                if self.is_sign_negative() {
100                    f.write_char('-')?;
101                }
102                let mut options = ToSciOptions::default();
103                options.set_base(16);
104                let m = u64::from(exponent.mod_power_of_2(2));
105                let mut p = precision.saturating_sub(m).shr_round(2, Ceiling).0;
106                if m != 0 {
107                    p += 1;
108                }
109                options.set_precision(p);
110                options.set_e_uppercase();
111                options.set_include_trailing_zeros(true);
112                if f.alternate() {
113                    f.write_str("0x")?;
114                }
115                let pr = precision.round_to_multiple_of_power_of_2(5, Up).0;
116                let s = if u64::from(exponent.unsigned_abs()) > (pr << 2) {
117                    let new_exponent = if *exponent > 0 {
118                        i32::exact_from(pr << 1)
119                    } else {
120                        -i32::exact_from(pr << 1)
121                    } + i32::wrapping_from(exponent.mod_power_of_2(2));
122                    let mut s = Rational::exact_from(self >> (exponent - new_exponent))
123                        .abs()
124                        .to_sci_with_options(options)
125                        .to_string();
126                    s = replace_exponent_in_hex_string(&s, (exponent - 1).shr_round(2, Floor).0);
127                    s
128                } else {
129                    Rational::exact_from(self)
130                        .abs()
131                        .to_sci_with_options(options)
132                        .to_string()
133                };
134                if s.contains('.') {
135                    write!(f, "{s}")
136                } else if let Some(i) = s.find('E') {
137                    write!(f, "{}.0E{}", &s[..i], &s[i + 1..])
138                } else {
139                    write!(f, "{s}.0")
140                }
141            }
142            _ => Display::fmt(&self, f),
143        }
144    }
145}
146
147impl Display for ComparableFloat {
148    #[inline]
149    fn fmt(&self, f: &mut Formatter) -> Result {
150        Display::fmt(&ComparableFloatRef(&self.0), f)
151    }
152}
153
154impl Debug for ComparableFloat {
155    #[inline]
156    fn fmt(&self, f: &mut Formatter) -> Result {
157        Debug::fmt(&ComparableFloatRef(&self.0), f)
158    }
159}
160
161impl LowerHex for ComparableFloat {
162    #[inline]
163    fn fmt(&self, f: &mut Formatter) -> Result {
164        LowerHex::fmt(&ComparableFloatRef(&self.0), f)
165    }
166}
167
168impl Display for ComparableFloatRef<'_> {
169    fn fmt(&self, f: &mut Formatter) -> Result {
170        if let x @ Float(Finite { precision, .. }) = &self.0 {
171            write!(f, "{x}")?;
172            f.write_char('#')?;
173            write!(f, "{precision}")
174        } else {
175            Display::fmt(&self.0, f)
176        }
177    }
178}
179
180impl LowerHex for ComparableFloatRef<'_> {
181    fn fmt(&self, f: &mut Formatter) -> Result {
182        if let x @ Float(Finite { precision, .. }) = &self.0 {
183            if f.alternate() {
184                write!(f, "{x:#x}")?;
185            } else {
186                write!(f, "{x:x}")?;
187            }
188            f.write_char('#')?;
189            write!(f, "{precision}")
190        } else {
191            LowerHex::fmt(&self.0, f)
192        }
193    }
194}
195
196impl Debug for ComparableFloatRef<'_> {
197    #[inline]
198    fn fmt(&self, f: &mut Formatter) -> Result {
199        Display::fmt(self, f)
200    }
201}