zng_unit/
float_eq.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
/// [`f32`] equality used in floating-point units.
///
/// * [`NaN`](f32::is_nan) values are equal.
/// * [`INFINITY`](f32::INFINITY) values are equal.
/// * [`NEG_INFINITY`](f32::NEG_INFINITY) values are equal.
/// * Finite values are equal if the difference is less than `epsilon`.
///
/// Note that this definition of equality is symmetric and reflexive, but it is **not** transitive, difference less then
/// epsilon can *accumulate* over a chain of comparisons breaking the transitive property:
///
/// ```
/// # use zng_unit::about_eq;
/// let e = 0.001;
/// let a = 0.0;
/// let b = a + e - 0.0001;
/// let c = b + e - 0.0001;
///
/// assert!(
///     about_eq(a, b, e) &&
///     about_eq(b, c, e) &&
///     !about_eq(a, c, e)
/// )
/// ```
///
/// See also [`about_eq_hash`].
pub fn about_eq(a: f32, b: f32, epsilon: f32) -> bool {
    if a.is_nan() {
        b.is_nan()
    } else if a.is_infinite() {
        b.is_infinite() && a.is_sign_positive() == b.is_sign_positive()
    } else {
        (a - b).abs() < epsilon
    }
}

/// [`f32`] hash compatible with [`about_eq`] equality.
pub fn about_eq_hash<H: std::hash::Hasher>(f: f32, epsilon: f32, state: &mut H) {
    let (group, f) = if f.is_nan() {
        (0u8, 0u64)
    } else if f.is_infinite() {
        (1, if f.is_sign_positive() { 1 } else { 2 })
    } else {
        let inv_epsilon = if epsilon > EQ_EPSILON_100 { 100000.0 } else { 100.0 };
        (2, ((f as f64) * inv_epsilon) as u64)
    };

    use std::hash::Hash;
    group.hash(state);
    f.hash(state);
}

/// [`f32`] ordering compatible with [`about_eq`] equality.
pub fn about_eq_ord(a: f32, b: f32, epsilon: f32) -> std::cmp::Ordering {
    if about_eq(a, b, epsilon) {
        std::cmp::Ordering::Equal
    } else if a > b {
        std::cmp::Ordering::Greater
    } else {
        std::cmp::Ordering::Less
    }
}

/// Minimal difference between values in around the 0.0..=1.0 scale.
pub const EQ_EPSILON: f32 = 0.00001;
/// Minimal difference between values in around the 1.0..=100.0 scale.
pub const EQ_EPSILON_100: f32 = 0.001;