matrixcompare/
ulp.rs

1//! Tools for ULP-based comparison of floating point numbers.
2use std::mem;
3
4/// Represents the result of an ULP-based comparison between two floating point numbers.
5#[derive(Debug, Copy, Clone, PartialEq)]
6pub enum UlpComparisonResult {
7    /// Signifies an exact match between two floating point numbers.
8    ExactMatch,
9    /// The difference in ULP between two floating point numbers.
10    Difference(u64),
11    /// The two floating point numbers have different signs,
12    /// and cannot be compared in a meaningful way.
13    IncompatibleSigns,
14    /// One or both of the two floating point numbers is a NaN,
15    /// in which case the ULP comparison is not meaningful.
16    Nan,
17}
18
19/// Floating point types for which two instances can be compared for Unit in the Last Place (ULP) difference.
20///
21/// Implementing this trait enables the usage of the `ulp` comparator in
22/// [assert_matrix_eq!](../macro.assert_matrix_eq!.html) for the given type.
23///
24/// The definition here leverages the fact that for two adjacent floating point numbers,
25/// their integer representations are also adjacent.
26///
27/// A somewhat accessible (but not exhaustive) guide on the topic is available in the popular article
28/// [Comparing Floating Point Numbers, 2012 Edition](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
29///
30/// Implementations for `f32` and `f64` are already available, and so users should not normally
31/// need to implement this. In the case when a custom implementation is necessary,
32/// please see the possible return values for [UlpComparisonResult].
33/// Otherwise, we can recommend to read the source code of the included `f32` and `f64` implementations.
34pub trait Ulp {
35    /// Returns the difference between two floating point numbers, measured in ULP.
36    fn ulp_diff(a: &Self, b: &Self) -> UlpComparisonResult;
37}
38
39macro_rules! impl_float_ulp {
40    ($ftype:ty, $itype:ty) => {
41        impl Ulp for $ftype {
42            #[allow(clippy::transmute_float_to_int)]
43            fn ulp_diff(a: &Self, b: &Self) -> UlpComparisonResult {
44                if a == b {
45                    UlpComparisonResult::ExactMatch
46                } else if a.is_nan() || b.is_nan() {
47                    // ULP comparison does not make much sense for NaN
48                    UlpComparisonResult::Nan
49                } else if a.is_sign_positive() != b.is_sign_positive() {
50                    // ULP is not meaningful when the signs of the two numbers differ
51                    UlpComparisonResult::IncompatibleSigns
52                } else {
53                    // Otherwise, we compute the ULP diff as the difference of the signed integer representations
54                    let a_int = unsafe { mem::transmute::<$ftype, $itype>(a.to_owned()) };
55                    let b_int = unsafe { mem::transmute::<$ftype, $itype>(b.to_owned()) };
56                    UlpComparisonResult::Difference((b_int - a_int).abs() as u64)
57                }
58            }
59        }
60    };
61}
62
63impl_float_ulp!(f32, i32);
64impl_float_ulp!(f64, i64);
65
66#[cfg(test)]
67mod tests {
68    use super::Ulp;
69    use super::UlpComparisonResult;
70    use quickcheck::TestResult;
71    use std::mem;
72    use std::{f32, f64};
73
74    #[test]
75    fn plus_minus_zero_is_exact_match_f32() {
76        assert!(f32::ulp_diff(&0.0, &0.0) == UlpComparisonResult::ExactMatch);
77        assert!(f32::ulp_diff(&-0.0, &-0.0) == UlpComparisonResult::ExactMatch);
78        assert!(f32::ulp_diff(&0.0, &-0.0) == UlpComparisonResult::ExactMatch);
79        assert!(f32::ulp_diff(&-0.0, &0.0) == UlpComparisonResult::ExactMatch);
80    }
81
82    #[test]
83    fn plus_minus_zero_is_exact_match_f64() {
84        assert!(f64::ulp_diff(&0.0, &0.0) == UlpComparisonResult::ExactMatch);
85        assert!(f64::ulp_diff(&-0.0, &-0.0) == UlpComparisonResult::ExactMatch);
86        assert!(f64::ulp_diff(&0.0, &-0.0) == UlpComparisonResult::ExactMatch);
87        assert!(f64::ulp_diff(&-0.0, &0.0) == UlpComparisonResult::ExactMatch);
88    }
89
90    #[test]
91    fn f32_double_nan() {
92        assert!(f32::ulp_diff(&f32::NAN, &f32::NAN) == UlpComparisonResult::Nan);
93    }
94
95    #[test]
96    fn f64_double_nan() {
97        assert!(f64::ulp_diff(&f64::NAN, &f64::NAN) == UlpComparisonResult::Nan);
98    }
99
100    quickcheck! {
101        fn property_exact_match_for_finite_f32_self_comparison(x: f32) -> TestResult {
102            if x.is_finite() {
103                TestResult::from_bool(f32::ulp_diff(&x, &x) == UlpComparisonResult::ExactMatch)
104            } else {
105                TestResult::discard()
106            }
107        }
108    }
109
110    quickcheck! {
111        fn property_exact_match_for_finite_f64_self_comparison(x: f64) -> TestResult {
112            if x.is_finite() {
113                TestResult::from_bool(f64::ulp_diff(&x, &x) == UlpComparisonResult::ExactMatch)
114            } else {
115                TestResult::discard()
116            }
117        }
118    }
119
120    quickcheck! {
121        fn property_recovers_ulp_diff_when_f32_constructed_from_i32(a: i32, b: i32) -> TestResult {
122            if a == b {
123                // Ignore self-comparisons, as it makes the below test have more complicated logic,
124                // and moreover we test self-comparisons in another property.
125                return TestResult::discard();
126            }
127
128            let x = unsafe { mem::transmute::<i32, f32>(a) };
129            let y = unsafe { mem::transmute::<i32, f32>(b) };
130
131            // Discard the input if it's non-finite or has different signs
132            if x.is_finite() && y.is_finite() && x.signum() == y.signum() {
133                TestResult::from_bool(f32::ulp_diff(&x, &y) == UlpComparisonResult::Difference((b - a).abs() as u64))
134            } else {
135                TestResult::discard()
136            }
137        }
138    }
139
140    quickcheck! {
141        fn property_recovers_ulp_diff_when_f64_constructed_from_i64(a: i64, b: i64) -> TestResult {
142            if a == b {
143                // Ignore self-comparisons, as it makes the below test have more complicated logic,
144                // and moreover we test self-comparisons in another property.
145                return TestResult::discard();
146            }
147
148            let x = unsafe { mem::transmute::<i64, f64>(a) };
149            let y = unsafe { mem::transmute::<i64, f64>(b) };
150
151            // Discard the input if it's non-finite or has different signs
152            if x.is_finite() && y.is_finite() && x.signum() == y.signum() {
153                TestResult::from_bool(f64::ulp_diff(&x, &y) == UlpComparisonResult::Difference((b - a).abs() as u64))
154            } else {
155                TestResult::discard()
156            }
157        }
158    }
159
160    quickcheck! {
161        fn property_f32_incompatible_signs_yield_corresponding_enum_value(x: f32, y: f32) -> TestResult {
162            if x.signum() == y.signum() {
163                TestResult::discard()
164            } else if x.is_nan() || y.is_nan() {
165                TestResult::discard()
166            } else {
167                TestResult::from_bool(f32::ulp_diff(&x, &y) == UlpComparisonResult::IncompatibleSigns)
168            }
169        }
170    }
171
172    quickcheck! {
173        fn property_f64_incompatible_signs_yield_corresponding_enum_value(x: f64, y: f64) -> TestResult {
174            if x.signum() == y.signum() {
175                TestResult::discard()
176            } else if x.is_nan() || y.is_nan() {
177                TestResult::discard()
178            } else {
179                TestResult::from_bool(f64::ulp_diff(&x, &y) == UlpComparisonResult::IncompatibleSigns)
180            }
181        }
182    }
183
184    quickcheck! {
185        fn property_f32_nan_gives_nan_enum_value(x: f32) -> bool {
186            f32::ulp_diff(&f32::NAN, &x) == UlpComparisonResult::Nan
187            && f32::ulp_diff(&x, &f32::NAN) == UlpComparisonResult::Nan
188        }
189    }
190
191    quickcheck! {
192        fn property_f64_nan_gives_nan_enum_value(x: f64) -> bool {
193            f64::ulp_diff(&f64::NAN, &x) == UlpComparisonResult::Nan
194            && f64::ulp_diff(&x, &f64::NAN) == UlpComparisonResult::Nan
195        }
196    }
197}