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