1use std::mem;
3
4#[derive(Debug, Copy, Clone, PartialEq)]
6pub enum UlpComparisonResult {
7 ExactMatch,
9 Difference(u64),
11 IncompatibleSigns,
14 Nan,
17}
18
19pub trait Ulp {
35 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 UlpComparisonResult::Nan
49 } else if a.is_sign_positive() != b.is_sign_positive() {
50 UlpComparisonResult::IncompatibleSigns
52 } else {
53 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 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 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 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 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}