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}