use crate::types::Real;
pub fn close(x: Real, y: Real) -> bool {
close_n(x, y, 42)
}
pub fn close_n(x: Real, y: Real, n: usize) -> bool {
#[allow(clippy::float_cmp)]
if x == y {
return true;
}
if !x.is_finite() || !y.is_finite() {
return false;
}
let diff = (x - y).abs();
let tolerance = n as Real * Real::EPSILON;
#[allow(clippy::float_cmp)]
if x == 0.0 || y == 0.0 {
return diff < tolerance * tolerance;
}
diff <= tolerance * x.abs() && diff <= tolerance * y.abs()
}
pub fn close_enough(x: Real, y: Real) -> bool {
close_enough_n(x, y, 42)
}
pub fn close_enough_n(x: Real, y: Real, n: usize) -> bool {
#[allow(clippy::float_cmp)]
if x == y {
return true;
}
if !x.is_finite() || !y.is_finite() {
return false;
}
let diff = (x - y).abs();
let tolerance = n as Real * Real::EPSILON;
#[allow(clippy::float_cmp)]
if x == 0.0 || y == 0.0 {
return diff < tolerance * tolerance;
}
diff <= tolerance * x.abs() || diff <= tolerance * y.abs()
}
pub(crate) fn sign(x: Real) -> Real {
if x > 0.0 {
1.0
} else if x < 0.0 {
-1.0
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_values_are_close() {
for x in [0.0, 1.0, -3.5, 1e300, Real::INFINITY] {
assert!(close(x, x));
}
}
#[test]
fn neighbouring_floats_are_close_but_distinct_ones_are_not() {
let x = 1.0;
assert!(close(x, x + Real::EPSILON));
assert!(!close(1.0, 1.0 + 1e-6));
}
#[test]
fn zero_uses_an_absolute_floor() {
assert!(close(0.0, 1e-30));
assert!(!close(0.0, 1e-20));
}
#[test]
fn strength_widens_the_tolerance() {
let x = 1.0;
let y = 1.0 + 50.0 * Real::EPSILON;
assert!(!close_n(x, y, 42));
assert!(close_n(x, y, 100));
}
#[test]
fn close_enough_is_the_looser_or_variant() {
let x = 1.0;
let y = 1.0 - 42.0 * Real::EPSILON;
assert!(!close(x, y));
assert!(close_enough(x, y));
assert!(close_enough(3.5, 3.5));
assert!(close_enough(0.0, 1e-30));
assert!(!close_enough(1.0, 1.0 + 1e-6));
}
#[test]
fn non_equal_non_finite_values_are_not_close() {
assert!(close(Real::INFINITY, Real::INFINITY));
assert!(close_enough(Real::NEG_INFINITY, Real::NEG_INFINITY));
for &(x, y) in &[
(Real::INFINITY, Real::NEG_INFINITY),
(Real::INFINITY, 1.0),
(Real::NAN, Real::NAN),
(Real::NAN, 0.0),
] {
assert!(!close(x, y), "close({x}, {y})");
assert!(!close_enough(x, y), "close_enough({x}, {y})");
}
}
}