precision_core/
tolerance.rs1use crate::decimal::Decimal;
4
5#[must_use]
9pub fn approx_eq(a: Decimal, b: Decimal, tolerance: Decimal) -> bool {
10 let diff = if a >= b { a - b } else { b - a };
11 diff <= tolerance
12}
13
14#[must_use]
20pub fn approx_eq_relative(a: Decimal, b: Decimal, relative_tolerance: Decimal) -> bool {
21 if a == b {
22 return true;
23 }
24
25 let diff = (a - b).abs();
26 let max_abs = a.abs().max(b.abs());
27
28 if max_abs.is_zero() {
29 return diff.is_zero();
30 }
31
32 if let Some(threshold) = max_abs.checked_mul(relative_tolerance) {
33 diff <= threshold
34 } else {
35 false
36 }
37}
38
39#[must_use]
45pub fn approx_eq_ulps(
46 a: Decimal,
47 b: Decimal,
48 absolute_tolerance: Decimal,
49 relative_tolerance: Decimal,
50) -> bool {
51 approx_eq(a, b, absolute_tolerance) || approx_eq_relative(a, b, relative_tolerance)
52}
53
54#[must_use]
58pub fn within_percentage(a: Decimal, b: Decimal, percentage: Decimal) -> bool {
59 if b.is_zero() {
60 return a.is_zero();
61 }
62
63 let diff = (a - b).abs();
64 let threshold = b
65 .abs()
66 .checked_mul(percentage)
67 .and_then(|v| v.checked_div(Decimal::ONE_HUNDRED));
68
69 match threshold {
70 Some(t) => diff <= t,
71 None => false,
72 }
73}
74
75#[must_use]
79pub fn within_basis_points(a: Decimal, b: Decimal, bps: Decimal) -> bool {
80 if b.is_zero() {
81 return a.is_zero();
82 }
83
84 let diff = (a - b).abs();
85 let threshold = b
86 .abs()
87 .checked_mul(bps)
88 .and_then(|v| v.checked_div(Decimal::new(10000, 0)));
89
90 match threshold {
91 Some(t) => diff <= t,
92 None => false,
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn approx_eq_exact() {
102 let a = Decimal::new(100, 2);
103 let tolerance = Decimal::new(1, 4);
104 assert!(approx_eq(a, a, tolerance));
105 }
106
107 #[test]
108 fn approx_eq_within_tolerance() {
109 let a = Decimal::new(1000, 3);
110 let b = Decimal::new(1001, 3);
111 let tolerance = Decimal::new(1, 3);
112 assert!(approx_eq(a, b, tolerance));
113 }
114
115 #[test]
116 fn approx_eq_outside_tolerance() {
117 let a = Decimal::new(1000, 3);
118 let b = Decimal::new(1002, 3);
119 let tolerance = Decimal::new(1, 3);
120 assert!(!approx_eq(a, b, tolerance));
121 }
122
123 #[test]
124 fn approx_eq_relative_basic() {
125 let a = Decimal::from(100i64);
126 let b = Decimal::from(101i64);
127 let tolerance = Decimal::new(2, 2); assert!(approx_eq_relative(a, b, tolerance));
129 }
130
131 #[test]
132 fn approx_eq_relative_large_values() {
133 let a = Decimal::from(1_000_000i64);
134 let b = Decimal::from(1_000_100i64);
135 let tolerance = Decimal::new(1, 3); assert!(approx_eq_relative(a, b, tolerance));
137 }
138
139 #[test]
140 fn within_percentage_basic() {
141 let a = Decimal::from(102i64);
142 let b = Decimal::from(100i64);
143 assert!(within_percentage(a, b, Decimal::from(5i64))); assert!(!within_percentage(a, b, Decimal::from(1i64))); }
146
147 #[test]
148 fn within_basis_points_basic() {
149 let a = Decimal::new(10010, 2); let b = Decimal::from(100i64);
151 assert!(within_basis_points(a, b, Decimal::from(100i64))); assert!(!within_basis_points(a, b, Decimal::from(5i64))); }
156
157 #[test]
158 fn within_basis_points_zero() {
159 let a = Decimal::from(100i64);
160 let b = Decimal::ZERO;
161 assert!(!within_basis_points(a, b, Decimal::from(100i64)));
162 assert!(within_basis_points(Decimal::ZERO, b, Decimal::from(100i64)));
163 }
164}