zng_unit/
float_eq.rs

1/// [`f32`] equality used in floating-point units.
2///
3/// * [`NaN`](f32::is_nan) values are equal.
4/// * [`INFINITY`](f32::INFINITY) values are equal.
5/// * [`NEG_INFINITY`](f32::NEG_INFINITY) values are equal.
6/// * Finite values are equal if the difference is less than `epsilon`.
7///
8/// Note that this definition of equality is symmetric and reflexive, but it is **not** transitive, difference less then
9/// epsilon can *accumulate* over a chain of comparisons breaking the transitive property:
10///
11/// ```
12/// # use zng_unit::about_eq;
13/// let e = 0.001;
14/// let a = 0.0;
15/// let b = a + e - 0.0001;
16/// let c = b + e - 0.0001;
17///
18/// assert!(
19///     about_eq(a, b, e) &&
20///     about_eq(b, c, e) &&
21///     !about_eq(a, c, e)
22/// )
23/// ```
24///
25/// See also [`about_eq_hash`].
26pub fn about_eq(a: f32, b: f32, epsilon: f32) -> bool {
27    if a.is_nan() {
28        b.is_nan()
29    } else if a.is_infinite() {
30        b.is_infinite() && a.is_sign_positive() == b.is_sign_positive()
31    } else {
32        (a - b).abs() < epsilon
33    }
34}
35
36/// [`f32`] hash compatible with [`about_eq`] equality.
37pub fn about_eq_hash<H: std::hash::Hasher>(f: f32, epsilon: f32, state: &mut H) {
38    let (group, f) = if f.is_nan() {
39        (0u8, 0u64)
40    } else if f.is_infinite() {
41        (1, if f.is_sign_positive() { 1 } else { 2 })
42    } else {
43        let inv_epsilon = if epsilon > EQ_EPSILON_100 { 100000.0 } else { 100.0 };
44        (2, ((f as f64) * inv_epsilon) as u64)
45    };
46
47    use std::hash::Hash;
48    group.hash(state);
49    f.hash(state);
50}
51
52/// [`f32`] ordering compatible with [`about_eq`] equality.
53pub fn about_eq_ord(a: f32, b: f32, epsilon: f32) -> std::cmp::Ordering {
54    if about_eq(a, b, epsilon) {
55        std::cmp::Ordering::Equal
56    } else if a > b {
57        std::cmp::Ordering::Greater
58    } else {
59        std::cmp::Ordering::Less
60    }
61}
62
63/// Minimal difference between values in around the 0.0..=1.0 scale.
64pub const EQ_EPSILON: f32 = 0.00001;
65/// Minimal difference between values in around the 1.0..=100.0 scale.
66pub const EQ_EPSILON_100: f32 = 0.001;