1use core::iter::zip;
4
5pub trait ApproxEq<Other: ?Sized = Self, Epsilon = Self> {
23 fn approx_eq(&self, other: &Other) -> bool {
26 self.approx_eq_eps(other, &Self::relative_epsilon())
27 }
28
29 fn approx_eq_eps(&self, other: &Other, rel_eps: &Epsilon) -> bool;
32
33 fn relative_epsilon() -> Epsilon;
35}
36
37impl ApproxEq for f32 {
38 fn approx_eq_eps(&self, other: &Self, rel_eps: &Self) -> bool {
39 use super::float::f32;
40 let diff = f32::abs(self - other);
41 diff <= *rel_eps * f32::abs(*self).max(1.0)
42 }
43
44 fn relative_epsilon() -> Self {
45 if cfg!(any(feature = "std", feature = "libm")) {
46 1e-6
47 } else {
48 5e-3
49 }
50 }
51}
52
53impl<E, T: Sized + ApproxEq<T, E>> ApproxEq<Self, E> for [T] {
54 fn approx_eq_eps(&self, other: &Self, rel_eps: &E) -> bool {
55 self.len() == other.len()
56 && zip(self, other).all(|(s, o)| s.approx_eq_eps(o, rel_eps))
57 }
58 fn relative_epsilon() -> E {
59 T::relative_epsilon()
60 }
61}
62
63impl<E, T: Sized + ApproxEq<T, E>, const N: usize> ApproxEq<Self, E>
64 for [T; N]
65{
66 fn approx_eq_eps(&self, other: &Self, rel_eps: &E) -> bool {
67 self.as_slice().approx_eq_eps(other, rel_eps)
68 }
69 fn relative_epsilon() -> E {
70 T::relative_epsilon()
71 }
72}
73
74impl<E, T: ApproxEq<T, E>> ApproxEq<Self, E> for Option<T> {
75 fn approx_eq_eps(&self, other: &Self, rel_eps: &E) -> bool {
76 match (self, other) {
77 (Some(s), Some(o)) => s.approx_eq_eps(o, rel_eps),
78 (Some(_), None) | (None, Some(_)) => false,
79 (None, None) => true,
80 }
81 }
82
83 fn relative_epsilon() -> E {
84 T::relative_epsilon()
85 }
86}
87
88#[macro_export]
123macro_rules! assert_approx_eq {
124 ($a:expr, $b:expr) => {
125 match (&$a, &$b) {
126 (a, b) => $crate::assert_approx_eq!(
127 *a, *b,
128 "assertion failed: `{a:?} ≅ {b:?}`"
129 )
130 }
131 };
132 ($a:expr, $b:expr, eps = $eps:literal) => {
133 match (&$a, &$b) {
134 (a, b) => $crate::assert_approx_eq!(
135 *a, *b, eps = $eps,
136 "assertion failed: `{a:?} ≅ {b:?}`"
137 )
138 }
139 };
140 ($a:expr, $b:expr, $fmt:literal $(, $args:expr)*) => {{
141 use $crate::math::approx::ApproxEq;
142 match (&$a, &$b) {
143 (a, b) => assert!(ApproxEq::approx_eq(a, b), $fmt $(, $args)*)
144 }
145 }};
146 ($a:expr, $b:expr, eps = $eps:literal, $fmt:literal $(, $args:expr)*) => {{
147 use $crate::math::approx::ApproxEq;
148 match (&$a, &$b) {
149 (a, b) => assert!(
150 ApproxEq::approx_eq_eps(a, b, &$eps),
151 $fmt $(, $args)*
152 )
153 }
154 }};
155}
156
157#[cfg(test)]
158mod tests {
159
160 mod f32 {
161 #[test]
162 fn approx_eq_zero() {
163 assert_approx_eq!(0.0, 0.0);
164 assert_approx_eq!(-0.0, 0.0);
165 assert_approx_eq!(0.0, -0.0);
166 }
167
168 #[test]
169 fn approx_eq_positive() {
170 assert_approx_eq!(0.0, 0.0000001);
171 assert_approx_eq!(0.0000001, 0.0);
172 assert_approx_eq!(0.9999999, 1.0);
173 assert_approx_eq!(1.0, 1.0000001);
174 assert_approx_eq!(1.0e10, 1.0000001e10);
175 }
176
177 #[test]
178 fn approx_eq_negative() {
179 assert_approx_eq!(0.0, -0.0000001);
180 assert_approx_eq!(-0.0000001, 0.0);
181 assert_approx_eq!(-1.0, -1.0000001);
182 assert_approx_eq!(-0.9999999, -1.0);
183 assert_approx_eq!(-1.0e10, -1.0000001e10);
184 }
185
186 #[test]
187 fn approx_eq_custom_epsilon() {
188 assert_approx_eq!(0.0, 0.001, eps = 0.01);
189 assert_approx_eq!(0.0, -0.001, eps = 0.01);
190 assert_approx_eq!(1.0, 0.999, eps = 0.01);
191 assert_approx_eq!(100.0, 99.9, eps = 0.01);
192 }
193
194 #[test]
195 #[should_panic]
196 fn zero_not_approx_eq_to_one() {
197 assert_approx_eq!(0.0, 1.0);
198 }
199 #[test]
200 #[should_panic]
201 fn one_not_approx_eq_to_1_00001() {
202 assert_approx_eq!(1.0, 1.00001);
203 }
204 #[test]
205 #[should_panic]
206 fn inf_not_approx_eq_to_inf() {
207 assert_approx_eq!(f32::INFINITY, f32::INFINITY);
208 }
209 #[test]
210 #[should_panic]
211 fn nan_not_approx_eq_to_nan() {
212 assert_approx_eq!(f32::NAN, f32::NAN);
213 }
214 }
215}