1use half::f16;
15
16pub trait UlpFloat: Copy {
18 const MAX_ULP: u64;
21
22 const SIGN_MASK: u64;
24
25 fn ulp_bits(self) -> u64;
30
31 fn ulp_is_nan(self) -> bool;
32
33 #[inline]
35 fn ulp_magnitude_bits(self) -> u64 {
36 self.ulp_bits() & !Self::SIGN_MASK
37 }
38
39 #[inline]
40 fn ulp_is_sign_negative(self) -> bool {
41 self.ulp_bits() & Self::SIGN_MASK != 0
42 }
43}
44
45macro_rules! impl_ulp_float {
46 ($t:ty, $bits:ty) => {
47 impl UlpFloat for $t {
48 const MAX_ULP: u64 = <$bits>::MAX as u64;
49 const SIGN_MASK: u64 = 1 << (<$bits>::BITS - 1);
50
51 #[inline]
52 fn ulp_bits(self) -> u64 {
53 self.to_bits() as u64
54 }
55
56 #[inline]
57 fn ulp_is_nan(self) -> bool {
58 <$t>::is_nan(self)
59 }
60 }
61 };
62}
63
64impl_ulp_float!(f16, u16);
65impl_ulp_float!(f32, u32);
66impl_ulp_float!(f64, u64);
67
68pub fn ulp_distance<T: UlpFloat>(a: T, b: T) -> u64 {
70 let (a_nan, b_nan) = (a.ulp_is_nan(), b.ulp_is_nan());
71 if a_nan && b_nan {
72 return 0;
73 }
74 if a_nan || b_nan {
75 return T::MAX_ULP;
76 }
77 if a.ulp_is_sign_negative() != b.ulp_is_sign_negative() {
78 return a
82 .ulp_magnitude_bits()
83 .saturating_add(b.ulp_magnitude_bits())
84 .saturating_add(1)
85 .min(T::MAX_ULP);
86 }
87 a.ulp_bits().abs_diff(b.ulp_bits())
88}
89
90pub fn max_ulp_distance<T: UlpFloat>(
95 a: impl IntoIterator<Item = T>,
96 b: impl IntoIterator<Item = T>,
97) -> (u64, Option<usize>) {
98 let mut worst = 0;
99 let mut at = None;
100 for (ix, (x, y)) in a.into_iter().zip(b).enumerate() {
101 let d = ulp_distance(x, y);
102 if d > worst || at.is_none() {
103 worst = d;
104 at = Some(ix);
105 }
106 }
107 (worst, at)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn adjacent_floats_are_one_ulp_apart() {
116 assert_eq!(ulp_distance(1.0f32, f32::from_bits(1.0f32.to_bits() + 1)), 1);
117 assert_eq!(ulp_distance(1.0f64, f64::from_bits(1.0f64.to_bits() + 1)), 1);
118 assert_eq!(
119 ulp_distance(f16::from_f32(1.0), f16::from_bits(f16::from_f32(1.0).to_bits() + 1)),
120 1
121 );
122 }
123
124 #[test]
125 fn identical_values_are_zero_ulp_apart() {
126 assert_eq!(ulp_distance(0.0f32, 0.0f32), 0);
127 assert_eq!(ulp_distance(-3.25f32, -3.25f32), 0);
128 assert_eq!(ulp_distance(f32::INFINITY, f32::INFINITY), 0);
129 }
130
131 #[test]
132 fn distance_is_symmetric_and_sign_aware() {
133 assert_eq!(ulp_distance(1.0f32, -1.0f32), ulp_distance(-1.0f32, 1.0f32));
134 assert_eq!(ulp_distance(0.0f32, -0.0f32), 1);
136 let tiny = f32::from_bits(1);
138 assert_eq!(ulp_distance(tiny, -tiny), 3);
139 }
140
141 #[test]
142 fn nan_handling() {
143 assert_eq!(ulp_distance(f32::NAN, f32::NAN), 0);
144 assert_eq!(ulp_distance(f32::NAN, 1.0f32), f32::MAX_ULP);
145 assert_eq!(ulp_distance(1.0f32, f32::NAN), f32::MAX_ULP);
146 }
147
148 #[test]
149 fn infinity_is_adjacent_to_max_finite() {
150 assert_eq!(ulp_distance(f32::MAX, f32::INFINITY), 1);
151 assert_eq!(ulp_distance(-f32::MAX, f32::NEG_INFINITY), 1);
152 }
153
154 #[test]
155 fn magnitude_independence() {
156 for scale in [1e-30f32, 1.0, 1e30] {
159 assert_eq!(ulp_distance(scale, f32::from_bits(scale.to_bits() + 1)), 1);
160 }
161 }
162
163 #[test]
164 fn max_over_slice_reports_position() {
165 let a = [1.0f32, 2.0, 3.0];
166 let b = [1.0f32, 2.0, f32::from_bits(3.0f32.to_bits() + 5)];
167 assert_eq!(max_ulp_distance(a, b), (5, Some(2)));
168 assert_eq!(max_ulp_distance::<f32>([], []), (0, None));
169 }
170}