test_helpers/
approxeq.rs

1//! Compare numeric types approximately.
2
3use float_cmp::Ulps;
4
5pub trait ApproxEq {
6    fn approxeq(&self, other: &Self, _ulps: i64) -> bool;
7    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result;
8}
9
10impl ApproxEq for bool {
11    fn approxeq(&self, other: &Self, _ulps: i64) -> bool {
12        self == other
13    }
14
15    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
16        write!(f, "{:?}", self)
17    }
18}
19
20macro_rules! impl_integer_approxeq {
21    { $($type:ty),* } => {
22        $(
23        impl ApproxEq for $type {
24            fn approxeq(&self, other: &Self, _ulps: i64) -> bool {
25                self == other
26            }
27
28            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
29                write!(f, "{:?} ({:x})", self, self)
30            }
31        }
32        )*
33    };
34}
35
36impl_integer_approxeq! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize }
37
38macro_rules! impl_float_approxeq {
39    { $($type:ty),* } => {
40        $(
41        impl ApproxEq for $type {
42            fn approxeq(&self, other: &Self, ulps: i64) -> bool {
43                if self.is_nan() && other.is_nan() {
44                    true
45                } else {
46                    (self.ulps(other) as i64).abs() <= ulps
47                }
48            }
49
50            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
51                write!(f, "{:?} ({:x})", self, self.to_bits())
52            }
53        }
54        )*
55    };
56}
57
58impl_float_approxeq! { f32, f64 }
59
60impl<T: ApproxEq, const N: usize> ApproxEq for [T; N] {
61    fn approxeq(&self, other: &Self, ulps: i64) -> bool {
62        self.iter()
63            .zip(other.iter())
64            .fold(true, |value, (left, right)| {
65                value && left.approxeq(right, ulps)
66            })
67    }
68
69    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
70        #[repr(transparent)]
71        struct Wrapper<'a, T: ApproxEq>(&'a T);
72
73        impl<T: ApproxEq> core::fmt::Debug for Wrapper<'_, T> {
74            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
75                self.0.fmt(f)
76            }
77        }
78
79        f.debug_list()
80            .entries(self.iter().map(|x| Wrapper(x)))
81            .finish()
82    }
83}
84
85#[doc(hidden)]
86pub struct ApproxEqWrapper<'a, T>(pub &'a T, pub i64);
87
88impl<T: ApproxEq> PartialEq<T> for ApproxEqWrapper<'_, T> {
89    fn eq(&self, other: &T) -> bool {
90        self.0.approxeq(other, self.1)
91    }
92}
93
94impl<T: ApproxEq> core::fmt::Debug for ApproxEqWrapper<'_, T> {
95    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
96        self.0.fmt(f)
97    }
98}
99
100#[macro_export]
101macro_rules! prop_assert_approxeq {
102    { $a:expr, $b:expr, $ulps:expr $(,)? } => {
103        {
104            use $crate::approxeq::ApproxEqWrapper;
105            let a = $a;
106            let b = $b;
107            proptest::prop_assert_eq!(ApproxEqWrapper(&a, $ulps), b);
108        }
109    };
110}