Skip to main content

malachite_base/num/float/
mod.rs

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