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}