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}