fast_float_compare/
lib.rs

1//! # Floating Point Comparison
2//!
3//! This crate provides a custom floating-point number representation (`Float`) that
4//! separates a floating-point number into its core components: mantissa, exponent, and sign.
5//! The primary purpose is to enable precise floating-point comparisons and benchmarking
6//! against other floating-point comparison methods.
7//!
8//! ## Features
9//! - Custom floating-point representation using mantissa, exponent, and sign
10//! - Conversion to and from f64
11//! - Precise comparison operations
12//! - Benchmarking infrastructure for comparison with other methods (e.g., Decimal)
13//!
14//! ## Example
15//! ```
16//! use fast_float_compare::Float;
17//!
18//! let a = Float::from_f64(1.23);
19//! let b = Float::from_f64(4.56);
20//!
21//! assert!(a < b);
22//!
23//! // Roundtrip conversion
24//! let value = 1.23;
25//! let raw = Float::from_f64(value).unwrap();
26//! let converted = raw.to_f64();
27//! assert!((value - converted).abs() < 1e-10);
28//! ```
29
30/// A custom floating-point number representation that separates the number into
31/// its fundamental components for more precise comparison operations.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Float {
34    /// The significant digits of the number, normalized to remove trailing zeros
35    mantissa: u64,
36    /// The power of 10 that the mantissa should be multiplied by
37    exponent: i16,
38    /// The sign of the number (true for positive, false for negative)
39    sign: bool,
40}
41
42impl Float {
43    pub const ZERO: Float = Float {
44        mantissa: 0,
45        exponent: 0,
46        sign: true,
47    };
48
49    pub const ONE: Float = Float {
50        mantissa: 0x10000000000000,
51        exponent: 1023,
52        sign: true,
53    };
54
55    pub const TWO: Float = Float {
56        mantissa: 0x10000000000000,
57        exponent: 1024,
58        sign: true,
59    };
60
61    fn new(mantissa: u64, exponent: i16, sign: bool) -> Self {
62        Self {
63            mantissa,
64            exponent,
65            sign,
66        }
67    }
68
69    /// Creates a new `Float` from a `f64` value.
70    ///
71    /// This method decomposes the floating-point number into its constituent parts,
72    /// normalizing the mantissa to remove trailing zeros and adjusting the exponent
73    /// accordingly.
74    ///
75    /// # Arguments
76    /// * `value` - The floating-point `f64` number to decompose.
77    ///
78    /// # Returns
79    /// A `Float` representation of the input number.
80    ///
81    /// # Example
82    /// ```
83    /// use fast_float_compare::Float;
84    ///
85    /// let num = Float::from_f64(1.23);
86    /// assert!(num.is_some());
87    /// ```
88    ///
89    /// # Errors
90    /// Returns `None` if the number is NaN or infinite.
91    pub fn from_f64(value: f64) -> Option<Self> {
92        if value.is_nan() || value.is_infinite() {
93            return None;
94        }
95
96        // Get the bits of the f64
97        let bits: u64 = value.to_bits();
98
99        // Get the sign bit
100        let sign = bits >> 63 == 0;
101
102        // Get the exponent
103        let exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
104
105        // Get the mantissa
106        let mantissa = if exponent == 0 {
107            // Subnormal number
108            (bits & 0xfffffffffffff) << 1
109        } else {
110            // Normal number
111            (bits & 0xfffffffffffff) | 0x10000000000000
112        };
113
114        // Return the Float
115        Some(Self::new(mantissa, exponent, sign))
116    }
117
118    /// Converts a `Float` back to an `f64`.
119    ///
120    /// This method reconstructs the original floating-point number from its
121    /// mantissa, exponent, and sign components.
122    ///
123    /// # Example
124    /// ```
125    /// use fast_float_compare::Float;
126    ///
127    /// let num = Float::from_f64(1.23).unwrap();
128    /// let value = num.to_f64();
129    /// assert_eq!(value, 1.23);
130    /// ```
131    ///
132    /// # Returns
133    /// The reconstructed `f64` value.
134    pub fn to_f64(&self) -> f64 {
135        let exponent = self.exponent;
136
137        // Calculate the bits
138        let sign_bit = if !self.sign { 1u64 << 63 } else { 0 };
139
140        // Handle subnormal numbers
141        let (mantissa_bits, exp_bits) = if exponent <= 0 {
142            // Subnormal number
143            ((self.mantissa >> 1) & 0xfffffffffffff, 0u64)
144        } else {
145            // Normal number
146            (self.mantissa & 0xfffffffffffff, (exponent as u64) << 52)
147        };
148
149        let bits = sign_bit | exp_bits | mantissa_bits;
150
151        // Convert bits back to f64
152        f64::from_bits(bits)
153    }
154}
155
156/// Implements the `Ord` trait for `Float`.
157/// This trait allows for comparison between `Float` values.
158///
159/// # Example
160/// ```
161/// use fast_float_compare::Float;
162///
163/// let a = Float::from_f64(1.23).unwrap();
164/// let b = Float::from_f64(4.56).unwrap();
165/// assert!(a < b);
166/// ```
167impl Ord for Float {
168    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
169        // First compare signs
170        match self.sign.cmp(&other.sign) {
171            std::cmp::Ordering::Equal => {
172                // For same signs, compare based on exponent and mantissa
173                if self.sign {
174                    // Positive numbers: larger exponent = larger number
175                    match self.exponent.cmp(&other.exponent) {
176                        std::cmp::Ordering::Equal => self.mantissa.cmp(&other.mantissa),
177                        ord => ord,
178                    }
179                } else {
180                    // Negative numbers: larger exponent = smaller number
181                    match self.exponent.cmp(&other.exponent) {
182                        std::cmp::Ordering::Equal => other.mantissa.cmp(&self.mantissa),
183                        ord => ord.reverse(),
184                    }
185                }
186            }
187
188            ord => ord,
189        }
190    }
191}
192
193impl PartialOrd for Float {
194    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
195        Some(self.cmp(other))
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_nans_and_infs() {
205        let nan = Float::from_f64(f64::NAN);
206        let inf = Float::from_f64(f64::INFINITY);
207        let neg_inf = Float::from_f64(f64::NEG_INFINITY);
208
209        assert!(nan.is_none());
210        assert!(inf.is_none());
211        assert!(neg_inf.is_none());
212    }
213
214    #[test]
215    fn test_const_values() {
216        assert!(Float::from_f64(0.0).unwrap() == Float::ZERO);
217        assert!(Float::from_f64(1.0).unwrap() == Float::ONE);
218        assert!(Float::from_f64(2.0).unwrap() == Float::TWO);
219    }
220
221    #[test]
222    fn test_f64_roundtrip() {
223        let test_values = vec![1.23, -4.56, 0.0, 1234.5678, -0.00123, 100.0, -100.0];
224
225        for &value in &test_values {
226            let raw = Float::from_f64(value);
227            let roundtrip = raw.unwrap().to_f64();
228            // Using approximate equality due to potential floating-point precision differences
229            assert!(
230                value == roundtrip,
231                "Failed roundtrip for {}: got {}",
232                value,
233                roundtrip
234            );
235        }
236    }
237
238    #[test]
239    fn test_cmp() {
240        let a = Float::from_f64(1.23);
241        let b = Float::from_f64(4.56);
242        assert!(a < b);
243
244        let c = Float::from_f64(-2.0);
245        let d = Float::from_f64(-2.0); // Same value for equality test
246        let e = Float::from_f64(1.23); // Same as a for equality test
247        let f = Float::from_f64(-5.67);
248
249        // Test less than
250        assert!(a < b);
251        assert!(c < b);
252        assert!(f < c);
253
254        // Test greater than
255        assert!(b > a);
256        assert!(b > c);
257        assert!(c > f);
258
259        // Test less than or equal to
260        assert!(a <= b);
261        assert!(c <= b);
262        assert!(c <= d);
263        assert!(a <= e);
264
265        // Test greater than or equal to
266        assert!(b >= a);
267        assert!(b >= c);
268        assert!(d >= c);
269        assert!(e >= a);
270
271        // Test equality
272        assert!(c == d);
273        assert!(a == e);
274        assert!(a != b);
275        assert!(c != f);
276    }
277
278    #[test]
279    fn test_ordering_vector() {
280        let values = [1.23, -4.56, 0.0, 1234.5678, -0.00123, 100.0, -100.0];
281        let mut sorted: Vec<Float> = values
282            .iter()
283            .map(|&n| Float::from_f64(n).unwrap())
284            .collect();
285        sorted.sort();
286
287        assert!(sorted.windows(2).all(|w| w[0] <= w[1]));
288    }
289}