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}
143