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}