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