malachite_base/num/float/
mod.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::num::basic::floats::PrimitiveFloat;
10use core::cmp::Ordering::{self, *};
11use core::fmt::{self, Debug, Display, Formatter};
12use core::hash::{Hash, Hasher};
13use core::str::FromStr;
14
15/// `NiceFloat` is a wrapper around primitive float types that provides nicer [`Eq`], [`Ord`],
16/// [`Hash`], [`Display`], and [`FromStr`] instances.
17///
18/// In most languages, floats behave weirdly due to the IEEE 754 standard. The `NiceFloat` type
19/// ignores the standard in favor of more intuitive behavior.
20/// * Using `NiceFloat`, `NaN`s are equal to themselves. There is a single, unique `NaN`; there's no
21///   concept of signalling `NaN`s. Positive and negative zero are two distinct values, not equal to
22///   each other.
23/// * The `NiceFloat` hash respects this equality.
24/// * `NiceFloat` has a total order. These are the classes of floats, in ascending order:
25///   - Negative infinity
26///   - Negative nonzero finite floats
27///   - Negative zero
28///   - NaN
29///   - Positive zero
30///   - Positive nonzero finite floats
31///   - Positive infinity
32/// * `NiceFloat` uses a different [`Display`] implementation than floats do by default in Rust. For
33///   example, Rust will format `f32::MIN_POSITIVE_SUBNORMAL` as something with many zeros, but
34///   `NiceFloat(f32::MIN_POSITIVE_SUBNORMAL)` just formats it as `"1.0e-45"`. The conversion
35///   function uses David Tolnay's [`ryu`](https://docs.rs/ryu/latest/ryu/) crate, with a few
36///   modifications:
37///   - All finite floats have a decimal point. For example, Ryu by itself would convert
38///     `f32::MIN_POSITIVE_SUBNORMAL` to `"1e-45"`.
39///   - Positive infinity, negative infinity, and NaN are converted to the strings `"Infinity"`,
40///     `"-Infinity"`, and "`NaN`", respectively.
41/// * [`FromStr`] accepts these strings.
42#[derive(Clone, Copy, Default)]
43pub struct NiceFloat<T: PrimitiveFloat>(pub T);
44
45#[derive(Eq, Ord, PartialEq, PartialOrd)]
46enum FloatType {
47    NegativeInfinity,
48    NegativeFinite,
49    NegativeZero,
50    NaN,
51    PositiveZero,
52    PositiveFinite,
53    PositiveInfinity,
54}
55
56impl<T: PrimitiveFloat> NiceFloat<T> {
57    fn float_type(self) -> FloatType {
58        let f = self.0;
59        if f.is_nan() {
60            FloatType::NaN
61        } else if f.sign() == Greater {
62            if f == T::ZERO {
63                FloatType::PositiveZero
64            } else if f.is_finite() {
65                FloatType::PositiveFinite
66            } else {
67                FloatType::PositiveInfinity
68            }
69        } else if f == T::ZERO {
70            FloatType::NegativeZero
71        } else if f.is_finite() {
72            FloatType::NegativeFinite
73        } else {
74            FloatType::NegativeInfinity
75        }
76    }
77}
78
79impl<T: PrimitiveFloat> PartialEq<NiceFloat<T>> for NiceFloat<T> {
80    /// Compares two `NiceFloat`s for equality.
81    ///
82    /// This implementation ignores the IEEE 754 standard in favor of an equality operation that
83    /// respects the expected properties of symmetry, reflexivity, and transitivity. Using
84    /// `NiceFloat`, `NaN`s are equal to themselves. There is a single, unique `NaN`; there's no
85    /// concept of signalling `NaN`s. Positive and negative zero are two distinct values, not equal
86    /// to each other.
87    ///
88    /// # Worst-case complexity
89    /// Constant time and additional memory.
90    ///
91    /// # Examples
92    /// ```
93    /// use malachite_base::num::float::NiceFloat;
94    ///
95    /// assert_eq!(NiceFloat(0.0), NiceFloat(0.0));
96    /// assert_eq!(NiceFloat(f32::NAN), NiceFloat(f32::NAN));
97    /// assert_ne!(NiceFloat(f32::NAN), NiceFloat(0.0));
98    /// assert_ne!(NiceFloat(0.0), NiceFloat(-0.0));
99    /// assert_eq!(NiceFloat(1.0), NiceFloat(1.0));
100    /// ```
101    #[inline]
102    fn eq(&self, other: &NiceFloat<T>) -> bool {
103        let f = self.0;
104        let g = other.0;
105        f.to_bits() == g.to_bits() || f.is_nan() && g.is_nan()
106    }
107}
108
109impl<T: PrimitiveFloat> Eq for NiceFloat<T> {}
110
111impl<T: PrimitiveFloat> Hash for NiceFloat<T> {
112    /// Computes a hash of a `NiceFloat`.
113    ///
114    /// The hash is compatible with `NiceFloat` equality: all `NaN`s hash to the same value.
115    ///
116    /// # Worst-case complexity
117    /// Constant time and additional memory.
118    fn hash<H: Hasher>(&self, state: &mut H) {
119        let f = self.0;
120        if f.is_nan() {
121            "NaN".hash(state);
122        } else {
123            f.to_bits().hash(state);
124        }
125    }
126}
127
128impl<T: PrimitiveFloat> Ord for NiceFloat<T> {
129    /// Compares two `NiceFloat`s.
130    ///
131    /// This implementation ignores the IEEE 754 standard in favor of a comparison operation that
132    /// respects the expected properties of antisymmetry, reflexivity, and transitivity. `NiceFloat`
133    /// has a total order. These are the classes of floats, in ascending order:
134    ///   - Negative infinity
135    ///   - Negative nonzero finite floats
136    ///   - Negative zero
137    ///   - NaN
138    ///   - Positive zero
139    ///   - Positive nonzero finite floats
140    ///   - Positive infinity
141    ///
142    /// # Worst-case complexity
143    /// Constant time and additional memory.
144    ///
145    /// # Examples
146    /// ```
147    /// use malachite_base::num::float::NiceFloat;
148    ///
149    /// assert!(NiceFloat(0.0) > NiceFloat(-0.0));
150    /// assert!(NiceFloat(f32::NAN) < NiceFloat(0.0));
151    /// assert!(NiceFloat(f32::NAN) > NiceFloat(-0.0));
152    /// assert!(NiceFloat(f32::INFINITY) > NiceFloat(f32::NAN));
153    /// assert!(NiceFloat(f32::NAN) < NiceFloat(1.0));
154    /// ```
155    fn cmp(&self, other: &NiceFloat<T>) -> Ordering {
156        let self_type = self.float_type();
157        let other_type = other.float_type();
158        self_type.cmp(&other_type).then_with(|| {
159            if self_type == FloatType::PositiveFinite || self_type == FloatType::NegativeFinite {
160                self.0.partial_cmp(&other.0).unwrap()
161            } else {
162                Equal
163            }
164        })
165    }
166}
167
168impl<T: PrimitiveFloat> PartialOrd<NiceFloat<T>> for NiceFloat<T> {
169    /// Compares a `NiceFloat` to another `NiceFloat`.
170    ///
171    /// See the documentation for the [`Ord`] implementation.
172    #[inline]
173    fn partial_cmp(&self, other: &NiceFloat<T>) -> Option<Ordering> {
174        Some(self.cmp(other))
175    }
176}
177
178#[doc(hidden)]
179pub trait FmtRyuString: Copy {
180    fn fmt_ryu_string(self, f: &mut Formatter<'_>) -> fmt::Result;
181}
182
183macro_rules! impl_fmt_ryu_string {
184    ($f: ident) => {
185        impl FmtRyuString for $f {
186            #[inline]
187            fn fmt_ryu_string(self, f: &mut Formatter<'_>) -> fmt::Result {
188                let mut buffer = ryu::Buffer::new();
189                let printed = buffer.format_finite(self);
190                // Convert e.g. "1e100" to "1.0e100". `printed` is ASCII, so we can manipulate bytes
191                // rather than chars.
192                let mut e_index = None;
193                let mut found_dot = false;
194                for (i, &b) in printed.as_bytes().iter().enumerate() {
195                    match b {
196                        b'.' => {
197                            found_dot = true;
198                            break; // If there's a '.', we don't need to do anything
199                        }
200                        b'e' => {
201                            e_index = Some(i);
202                            break; // OK to break since there won't be a '.' after an 'e'
203                        }
204                        _ => {}
205                    }
206                }
207                if found_dot {
208                    f.write_str(printed)
209                } else {
210                    if let Some(e_index) = e_index {
211                        let mut out_bytes = ::alloc::vec![0; printed.len() + 2];
212                        let (in_bytes_lo, in_bytes_hi) = printed.as_bytes().split_at(e_index);
213                        let (out_bytes_lo, out_bytes_hi) = out_bytes.split_at_mut(e_index);
214                        out_bytes_lo.copy_from_slice(in_bytes_lo);
215                        out_bytes_hi[0] = b'.';
216                        out_bytes_hi[1] = b'0';
217                        out_bytes_hi[2..].copy_from_slice(in_bytes_hi);
218                        f.write_str(core::str::from_utf8(&out_bytes).unwrap())
219                    } else {
220                        panic!("Unexpected Ryu string: {}", printed);
221                    }
222                }
223            }
224        }
225    };
226}
227
228impl_fmt_ryu_string!(f32);
229
230impl_fmt_ryu_string!(f64);
231
232impl<T: PrimitiveFloat> Display for NiceFloat<T> {
233    /// Formats a `NiceFloat` as a string.
234    ///
235    /// `NiceFloat` uses a different [`Display`] implementation than floats do by default in Rust.
236    /// For example, Rust will convert `f32::MIN_POSITIVE_SUBNORMAL` to something with many zeros,
237    /// but `NiceFloat(f32::MIN_POSITIVE_SUBNORMAL)` just converts to `"1.0e-45"`. The conversion
238    /// function uses David Tolnay's [`ryu`](https://docs.rs/ryu/latest/ryu/) crate, with a few
239    /// modifications:
240    /// - All finite floats have a decimal point. For example, Ryu by itself would convert
241    ///   `f32::MIN_POSITIVE_SUBNORMAL` to `"1e-45"`.
242    /// - Positive infinity, negative infinity, and NaN are converted to the strings `"Infinity"`,
243    ///   `"-Infinity"`, and "`NaN`", respectively.
244    ///
245    /// # Worst-case complexity
246    /// Constant time and additional memory.
247    ///
248    /// # Examples
249    /// ```
250    /// use malachite_base::num::basic::floats::PrimitiveFloat;
251    /// use malachite_base::num::basic::traits::NegativeInfinity;
252    /// use malachite_base::num::float::NiceFloat;
253    ///
254    /// assert_eq!(NiceFloat(0.0).to_string(), "0.0");
255    /// assert_eq!(NiceFloat(-0.0).to_string(), "-0.0");
256    /// assert_eq!(NiceFloat(f32::INFINITY).to_string(), "Infinity");
257    /// assert_eq!(NiceFloat(f32::NEGATIVE_INFINITY).to_string(), "-Infinity");
258    /// assert_eq!(NiceFloat(f32::NAN).to_string(), "NaN");
259    ///
260    /// assert_eq!(NiceFloat(1.0).to_string(), "1.0");
261    /// assert_eq!(NiceFloat(-1.0).to_string(), "-1.0");
262    /// assert_eq!(
263    ///     NiceFloat(f32::MIN_POSITIVE_SUBNORMAL).to_string(),
264    ///     "1.0e-45"
265    /// );
266    /// assert_eq!(
267    ///     NiceFloat(std::f64::consts::E).to_string(),
268    ///     "2.718281828459045"
269    /// );
270    /// assert_eq!(
271    ///     NiceFloat(std::f64::consts::PI).to_string(),
272    ///     "3.141592653589793"
273    /// );
274    /// ```
275    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
276        if self.0.is_nan() {
277            f.write_str("NaN")
278        } else if self.0.is_infinite() {
279            if self.0.sign() == Greater {
280                f.write_str("Infinity")
281            } else {
282                f.write_str("-Infinity")
283            }
284        } else {
285            self.0.fmt_ryu_string(f)
286        }
287    }
288}
289
290impl<T: PrimitiveFloat> Debug for NiceFloat<T> {
291    /// Formats a `NiceFloat` as a string.
292    ///
293    /// This is identical to the [`Display::fmt`] implementation.
294    #[inline]
295    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
296        Display::fmt(self, f)
297    }
298}
299
300impl<T: PrimitiveFloat> FromStr for NiceFloat<T> {
301    type Err = <T as FromStr>::Err;
302
303    /// Converts a `&str` to a `NiceFloat`.
304    ///
305    /// If the `&str` does not represent a valid `NiceFloat`, an `Err` is returned.
306    ///
307    /// # Worst-case complexity
308    /// $T(n) = O(n)$
309    ///
310    /// $M(n) = O(1)$
311    ///
312    /// where $T$ is time, $M$ is additional memory, and $n$ = `src.len()`.
313    ///
314    /// # Examples
315    /// ```
316    /// use malachite_base::num::float::NiceFloat;
317    /// use std::str::FromStr;
318    ///
319    /// assert_eq!(NiceFloat::from_str("NaN").unwrap(), NiceFloat(f32::NAN));
320    /// assert_eq!(NiceFloat::from_str("-0.00").unwrap(), NiceFloat(-0.0f64));
321    /// assert_eq!(NiceFloat::from_str(".123").unwrap(), NiceFloat(0.123f32));
322    /// ```
323    #[inline]
324    fn from_str(src: &str) -> Result<NiceFloat<T>, <T as FromStr>::Err> {
325        match src {
326            "NaN" => Ok(T::NAN),
327            "Infinity" => Ok(T::INFINITY),
328            "-Infinity" => Ok(T::NEGATIVE_INFINITY),
329            "inf" | "-inf" => T::from_str("invalid"),
330            src => T::from_str(src),
331        }
332        .map(NiceFloat)
333    }
334}