eq_float/
lib.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5#[derive(Debug, Default, Clone, Copy)]
6pub struct F32(pub f32);
7
8/// This works like `PartialEq` on `f32`, except that `NAN == NAN` is true.
9impl PartialEq for F32 {
10    fn eq(&self, other: &Self) -> bool {
11        if self.0.is_nan() && other.0.is_nan() {
12            true
13        } else {
14            self.0 == other.0
15        }
16    }
17}
18
19impl Eq for F32 {}
20
21/// This works like `PartialOrd` on `f32`, except that `NAN` sorts below all other floats
22/// (and is equal to another NAN). This always returns a `Some`.
23impl PartialOrd for F32 {
24    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
25        Some(self.cmp(other))
26    }
27}
28
29/// This works like `PartialOrd` on `f32`, except that `NAN` sorts below all other floats
30/// (and is equal to another NAN).
31impl Ord for F32 {
32    fn cmp(&self, other: &Self) -> Ordering {
33        self.0.partial_cmp(&other.0).unwrap_or_else(|| {
34            if self.0.is_nan() && !other.0.is_nan() {
35                Ordering::Less
36            } else if !self.0.is_nan() && other.0.is_nan() {
37                Ordering::Greater
38            } else {
39                Ordering::Equal
40            }
41        })
42    }
43}
44
45impl Hash for F32 {
46    fn hash<H: Hasher>(&self, state: &mut H) {
47        if self.0.is_nan() {
48            0x7fc00000u32.hash(state); // a particular bit representation for NAN
49        } else if self.0 == 0.0 { // catches both positive and negative zero
50            0u32.hash(state);
51        } else {
52            self.0.to_bits().hash(state);
53        }
54    }
55}
56
57impl From<F32> for f32 {
58    fn from(f: F32) -> Self {
59        f.0
60    }
61}
62
63impl From<f32> for F32 {
64    fn from(f: f32) -> Self {
65        F32(f)
66    }
67}
68
69impl fmt::Display for F32 {
70    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71        self.0.fmt(f)
72    }
73}
74
75#[derive(Debug, Default, Clone, Copy)]
76pub struct F64(pub f64);
77
78/// This works like `PartialEq` on `f64`, except that `NAN == NAN` is true.
79impl PartialEq for F64 {
80    fn eq(&self, other: &Self) -> bool {
81        if self.0.is_nan() && other.0.is_nan() {
82            true
83        } else {
84            self.0 == other.0
85        }
86    }
87}
88
89impl Eq for F64 {}
90
91/// This works like `PartialOrd` on `f64`, except that `NAN` sorts below all other floats
92/// (and is equal to another NAN). This always returns a `Some`.
93impl PartialOrd for F64 {
94    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
95        Some(self.cmp(other))
96    }
97}
98
99/// This works like `PartialOrd` on `f64`, except that `NAN` sorts below all other floats
100/// (and is equal to another NAN).
101impl Ord for F64 {
102    fn cmp(&self, other: &Self) -> Ordering {
103        self.0.partial_cmp(&other.0).unwrap_or_else(|| {
104            if self.0.is_nan() && !other.0.is_nan() {
105                Ordering::Less
106            } else if !self.0.is_nan() && other.0.is_nan() {
107                Ordering::Greater
108            } else {
109                Ordering::Equal
110            }
111        })
112    }
113}
114
115impl Hash for F64 {
116    fn hash<H: Hasher>(&self, state: &mut H) {
117        if self.0.is_nan() {
118            0x7ff8000000000000u64.hash(state); // a particular bit representation for NAN
119        } else if self.0 == 0.0 { // catches both positive and negative zero
120            0u64.hash(state);
121        } else {
122            self.0.to_bits().hash(state);
123        }
124    }
125}
126
127impl From<F64> for f64 {
128    fn from(f: F64) -> Self {
129        f.0
130    }
131}
132
133impl From<f64> for F64 {
134    fn from(f: f64) -> Self {
135        F64(f)
136    }
137}
138
139impl fmt::Display for F64 {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        self.0.fmt(f)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use std::collections::hash_map::DefaultHasher;
148    use std::hash::{Hash, Hasher};
149
150    use super::{F32, F64};
151
152    fn calculate_hash<T: Hash>(t: &T) -> u64 {
153        let mut s = DefaultHasher::new();
154        t.hash(&mut s);
155        s.finish()
156    }
157
158    #[test]
159    fn f32_eq() {
160        assert!(F32(std::f32::NAN) == F32(std::f32::NAN));
161        assert!(F32(std::f32::NAN) != F32(5.0));
162        assert!(F32(5.0) != F32(std::f32::NAN));
163        assert!(F32(0.0) == F32(-0.0));
164    }
165
166    #[test]
167    fn f32_cmp() {
168        assert!(F32(std::f32::NAN) == F32(std::f32::NAN));
169        assert!(F32(std::f32::NAN) < F32(5.0));
170        assert!(F32(5.0) > F32(std::f32::NAN));
171        assert!(F32(0.0) == F32(-0.0));
172    }
173
174    #[test]
175    fn f32_hash() {
176        assert!(calculate_hash(&F32(0.0)) == calculate_hash(&F32(-0.0)));
177        assert!(calculate_hash(&F32(std::f32::NAN)) == calculate_hash(&F32(-std::f32::NAN)));
178    }
179
180    #[test]
181    fn f64_eq() {
182        assert!(F64(std::f64::NAN) == F64(std::f64::NAN));
183        assert!(F64(std::f64::NAN) != F64(5.0));
184        assert!(F64(5.0) != F64(std::f64::NAN));
185        assert!(F64(0.0) == F64(-0.0));
186    }
187
188    #[test]
189    fn f64_cmp() {
190        assert!(F64(std::f64::NAN) == F64(std::f64::NAN));
191        assert!(F64(std::f64::NAN) < F64(5.0));
192        assert!(F64(5.0) > F64(std::f64::NAN));
193        assert!(F64(0.0) == F64(-0.0));
194    }
195
196    #[test]
197    fn f64_hash() {
198        assert!(calculate_hash(&F64(0.0)) == calculate_hash(&F64(-0.0)));
199        assert!(calculate_hash(&F64(std::f64::NAN)) == calculate_hash(&F64(-std::f64::NAN)));
200    }
201}