Floating Point Edge Cases
Floating Point Edge Cases
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.
Special Values
Floating-point types have special values that represent unusual mathematical concepts:
- NaN (Not a Number): Result of invalid operations like
0.0/0.0or(-1.0_f64).sqrt(). NaN is unique because it's not equal to anything, including itself! - Infinity: Result of overflow or dividing by zero (e.g.,
1.0/0.0gives positive infinity) - Negative Zero:
-0.0exists and equals0.0, but can be distinguished
let 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);Checking for Special Values
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 finiteRounding and Truncation
Rust 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 -3Approximate Comparisons
Due 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 closeYour Task
Implement the following functions to handle floating-point edge cases:
-
is_valid_number(x: f64) -> bool- Returns
trueifxis a finite, non-NaN number (i.e., a "normal" number you can do math with).
- Returns
-
classify_float(x: f64) -> &'static str- Returns a string describing the float:
"nan","infinite","zero","normal", or"subnormal".
- Returns a string describing the float:
-
safe_divide(a: f64, b: f64) -> Option<f64>- Divides
abyb, returningNoneifbis zero or if either input is NaN/infinite.
- Divides
-
round_to_places(x: f64, places: u32) -> f64- Rounds
xto the specified number of decimal places.
- Rounds
-
approx_equal(a: f64, b: f64, epsilon: f64) -> bool- Returns
trueifaandbare withinepsilonof each other. Returnsfalseif either is NaN.
- Returns
-
clamp_to_range(x: f64, min: f64, max: f64) -> Option<f64>- Clamps
xto the range[min, max]. ReturnsNoneif any input is NaN or ifmin > max.
- Clamps
-
safe_sqrt(x: f64) -> Option<f64>- Returns the square root ofx, orNoneifxis negative or NaN. -
sum_finite(numbers: &[f64]) -> f64- Sums all finite numbers in the slice, skipping any NaN or infinite values.
Examples
// 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
);Hints
Click here to reveal hints
- Use
x.is_finite()to check if a number is neither NaN nor infinite. - Use
x.is_nan()specifically to check for NaN values. - The
x.classify()method returns anFpCategoryenum with variantsNan,Infinite,Zero,Subnormal, andNormal. - For
round_to_places, multiply by 10^places, round, then divide by 10^places. - For
approx_equal, compute(a - b).abs()and compare to epsilon. Remember that any comparison with NaN returns false. - The
x.clamp(min, max)method exists but doesn't handle NaN the way we want - you'll need custom logic. - For
safe_sqrt, check both thatx >= 0.0and thatxis not NaN. - Use
filterwithis_finite()before summing insum_finite.
Run to execute your code and see test results here.