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