float_cmp/
ratio.rs

1// Copyright 2014-2020 Optimal Computing (NZ) Ltd.
2// Licensed under the MIT license.  See LICENSE for details.
3
4use core::cmp::PartialOrd;
5use core::ops::{Sub, Div, Neg};
6use num_traits::Zero;
7
8/// ApproxEqRatio is a trait for approximate equality comparisons bounding the ratio
9/// of the difference to the larger.
10pub trait ApproxEqRatio : Div<Output = Self> + Sub<Output = Self> + Neg<Output = Self>
11    + PartialOrd + Zero + Sized + Copy
12{
13    /// This method tests if `self` and `other` are nearly equal by bounding the
14    /// difference between them to some number much less than the larger of the two.
15    /// This bound is set as the ratio of the difference to the larger.
16    fn approx_eq_ratio(&self, other: &Self, ratio: Self) -> bool {
17
18        // Not equal if signs are not equal
19        if *self < Self::zero() && *other > Self::zero() { return false; }
20        if *self > Self::zero() && *other < Self::zero() { return false; }
21
22        // Handle all zero cases
23        match (*self == Self::zero(), *other == Self::zero()) {
24            (true,true) => return true,
25            (true,false) => return false,
26            (false,true) => return false,
27            _ => { }
28        }
29
30        // abs
31        let (s,o) = if *self < Self::zero() {
32            (-*self, -*other)
33        } else {
34            (*self, *other)
35        };
36
37        let (smaller,larger) = if s < o {
38            (s,o)
39        } else {
40            (o,s)
41        };
42        let difference: Self = larger.sub(smaller);
43        let actual_ratio: Self = difference.div(larger);
44        actual_ratio < ratio
45    }
46
47    /// This method tests if `self` and `other` are not nearly equal by bounding the
48    /// difference between them to some number much less than the larger of the two.
49    /// This bound is set as the ratio of the difference to the larger.
50    #[inline]
51    fn approx_ne_ratio(&self, other: &Self, ratio: Self) -> bool {
52        !self.approx_eq_ratio(other, ratio)
53    }
54}
55
56impl ApproxEqRatio for f32 { }
57
58#[test]
59fn f32_approx_eq_ratio_test1() {
60    let x: f32 = 0.00004_f32;
61    let y: f32 = 0.00004001_f32;
62    assert!(x.approx_eq_ratio(&y, 0.00025));
63    assert!(y.approx_eq_ratio(&x, 0.00025));
64    assert!(x.approx_ne_ratio(&y, 0.00024));
65    assert!(y.approx_ne_ratio(&x, 0.00024));
66}
67
68#[test]
69fn f32_approx_eq_ratio_test2() {
70    let x: f32 = 0.00000000001_f32;
71    let y: f32 = 0.00000000005_f32;
72    assert!(x.approx_eq_ratio(&y, 0.81));
73    assert!(y.approx_ne_ratio(&x, 0.79));
74}
75
76#[test]
77fn f32_approx_eq_ratio_test_zero_eq_zero_returns_true() {
78    let x: f32 = 0.0_f32;
79    assert!(x.approx_eq_ratio(&x,0.1) == true);
80}
81
82#[test]
83fn f32_approx_eq_ratio_test_zero_ne_zero_returns_false() {
84    let x: f32 = 0.0_f32;
85    assert!(x.approx_ne_ratio(&x,0.1) == false);
86}
87
88#[test]
89fn f32_approx_eq_ratio_test_against_a_zero_is_false() {
90    let x: f32 = 0.0_f32;
91    let y: f32 = 0.1_f32;
92    assert!(x.approx_eq_ratio(&y,0.1) == false);
93    assert!(y.approx_eq_ratio(&x,0.1) == false);
94}
95
96#[test]
97fn f32_approx_eq_ratio_test_negative_numbers() {
98    let x: f32 = -3.0_f32;
99    let y: f32 = -4.0_f32;
100    // -3 and -4 should not be equal at a ratio of 0.1
101    assert!(x.approx_eq_ratio(&y,0.1) == false);
102}
103
104impl ApproxEqRatio for f64 { }
105
106#[test]
107fn f64_approx_eq_ratio_test1() {
108    let x: f64 = 0.000000004_f64;
109    let y: f64 = 0.000000004001_f64;
110    assert!(x.approx_eq_ratio(&y, 0.00025));
111    assert!(y.approx_eq_ratio(&x, 0.00025));
112    assert!(x.approx_ne_ratio(&y, 0.00024));
113    assert!(y.approx_ne_ratio(&x, 0.00024));
114}
115
116#[test]
117fn f64_approx_eq_ratio_test2() {
118    let x: f64 = 0.0000000000000001_f64;
119    let y: f64 = 0.0000000000000005_f64;
120    assert!(x.approx_eq_ratio(&y, 0.81));
121    assert!(y.approx_ne_ratio(&x, 0.79));
122}
123
124#[test]
125fn f64_approx_eq_ratio_test_zero_eq_zero_returns_true() {
126    let x: f64 = 0.0_f64;
127    assert!(x.approx_eq_ratio(&x,0.1) == true);
128}
129
130#[test]
131fn f64_approx_eq_ratio_test_zero_ne_zero_returns_false() {
132    let x: f64 = 0.0_f64;
133    assert!(x.approx_ne_ratio(&x,0.1) == false);
134}
135
136#[test]
137fn f64_approx_eq_ratio_test_negative_numbers() {
138    let x: f64 = -3.0_f64;
139    let y: f64 = -4.0_f64;
140    // -3 and -4 should not be equal at a ratio of 0.1
141    assert!(x.approx_eq_ratio(&y,0.1) == false);
142}