Floating-point numbers in Rust (f32 and f64) follow the IEEE 754 standard, which includes some surprising behaviors that can trip up even experienced programmers. Understanding these edge cases is crucial for writing robust numerical code.
Floating-point types have special values that represent unusual mathematical concepts:
0.0/0.0 or (-1.0_f64).sqrt(). NaN is unique because it's not equal to anything, including itself!1.0/0.0 gives positive infinity)-0.0 exists and equals 0.0, but can be distinguishedlet nan = f64::NAN;
assert!(nan != nan); // NaN is never equal to itself!
assert!(nan.is_nan());
let inf = f64::INFINITY;
assert!(inf.is_infinite());
assert!(inf > f64::MAX);
// Negative zero equals positive zero
assert_eq!(0.0, -0.0);The standard library provides methods to detect these special values:
let x = 1.0_f64;
assert!(x.is_finite()); // Not NaN, not infinite
assert!(!x.is_nan());
assert!(!x.is_infinite());
// Not zero, subnormal, infinite, or NaN
assert!(x.is_normal());
let zero = 0.0_f64;
assert!(!zero.is_normal()); // Zero is not "normal"
assert!(zero.is_finite()); // But it is finiteRust provides several methods for converting floats to integers or rounding:
let x = 3.7_f64;
// Round toward negative infinity
assert_eq!(x.floor(), 3.0);
// Round toward positive infinity
assert_eq!(x.ceil(), 4.0);
// Round to nearest, ties away from zero
assert_eq!(x.round(), 4.0);
assert_eq!(x.trunc(), 3.0); // Round toward zero
let y = -3.7_f64;
assert_eq!(y.floor(), -4.0); // -4 is "more negative"
assert_eq!(y.trunc(), -3.0); // Toward zero is -3Due to floating-point precision limits, direct equality comparison is often unreliable:
let a = 0.1 + 0.2;
let b = 0.3;
assert!(a != b); // Surprise! They're not exactly equal
assert!((a - b).abs() < 1e-10); // But they're very closeImplement the following functions to handle floating-point edge cases:
is_valid_number(x: f64) -> bool
true if x is a finite, non-NaN number
(i.e., a "normal" number you can do math with).classify_float(x: f64) -> &'static str
"nan",
"infinite", "zero", "normal", or "subnormal".safe_divide(a: f64, b: f64) -> Option<f64>
a by b, returning None if b is zero
or if either input is NaN/infinite.round_to_places(x: f64, places: u32) -> f64
x to the specified number of decimal places.approx_equal(a: f64, b: f64, epsilon: f64) -> bool
true if a and b are within epsilon
of each other. Returns false if either is NaN.clamp_to_range(x: f64, min: f64, max: f64) -> Option<f64>
x to the range [min, max]. Returns None
if any input is NaN or if min > max.safe_sqrt(x: f64) -> Option<f64> - Returns the square root of x, or None if x is negative or NaN.
sum_finite(numbers: &[f64]) -> f64 - Sums all finite numbers in the slice, skipping any NaN or infinite values.
// Validation
assert!(is_valid_number(42.0));
assert!(!is_valid_number(f64::NAN));
assert!(!is_valid_number(f64::INFINITY));
// Classification
assert_eq!(classify_float(f64::NAN), "nan");
assert_eq!(classify_float(0.0), "zero");
assert_eq!(classify_float(1.5), "normal");
// Safe division
assert_eq!(safe_divide(10.0, 2.0), Some(5.0));
assert_eq!(safe_divide(10.0, 0.0), None);
assert_eq!(safe_divide(f64::NAN, 2.0), None);
// Rounding
assert_eq!(round_to_places(3.14159, 2), 3.14);
assert_eq!(round_to_places(2.5, 0), 3.0);
// Approximate equality
assert!(approx_equal(0.1 + 0.2, 0.3, 1e-10));
assert!(!approx_equal(1.0, 2.0, 0.5));
// Clamping
assert_eq!(clamp_to_range(5.0, 0.0, 10.0), Some(5.0));
assert_eq!(clamp_to_range(-5.0, 0.0, 10.0), Some(0.0));
assert_eq!(clamp_to_range(f64::NAN, 0.0, 10.0), None);
// Safe sqrt
assert_eq!(safe_sqrt(4.0), Some(2.0));
assert_eq!(safe_sqrt(-1.0), None);
// Sum finite values
assert_eq!(
sum_finite(&[1.0, f64::NAN, 2.0, f64::INFINITY, 3.0]),
6.0
);x.is_finite() to check if a number is neither NaN nor infinite.x.is_nan() specifically to check for NaN values.x.classify() method returns an FpCategory enum with variants Nan, Infinite, Zero, Subnormal, and Normal.round_to_places, multiply by 10^places, round, then divide by 10^places.approx_equal, compute (a - b).abs() and compare to epsilon. Remember that any comparison with NaN returns false.x.clamp(min, max) method exists but doesn't handle NaN the way we want - you'll need custom logic.safe_sqrt, check both that x >= 0.0 and that x is not NaN.filter with is_finite() before summing in sum_finite.