typed_floats/types/impls/
hash.rs

1use crate::{
2    Negative, NegativeFinite, NonNaN, NonNaNFinite, NonZeroNonNaN, NonZeroNonNaNFinite, Positive,
3    PositiveFinite, StrictlyNegative, StrictlyNegativeFinite, StrictlyPositive,
4    StrictlyPositiveFinite,
5};
6
7// > When implementing both Hash and Eq, it is important that the following property holds:
8// > `k1 == k2 -> hash(k1) == hash(k2)`
9// This is sound because `NaN` is not a possible value.
10// https://doc.rust-lang.org/core/hash/trait.Hash.html
11
12impl core::hash::Hash for NonNaN<f32> {
13    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
14        // `+0.0` and `-0.0` are equal to they must have the same hash
15        // -0.0 + 0.0 == +0.0 with IEEE754 roundTiesToEven use by rust
16        (self.0 + 0.0).to_bits().hash(state);
17    }
18}
19
20impl core::hash::Hash for NonNaN<f64> {
21    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
22        // `+0.0` and `-0.0` are equal to they must have the same hash
23        // -0.0 + 0.0 == +0.0 with IEEE754 roundTiesToEven use by rust
24        (self.0 + 0.0).to_bits().hash(state);
25    }
26}
27
28impl core::hash::Hash for NonNaNFinite<f32> {
29    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
30        // `+0.0` and `-0.0` are equal to they must have the same hash
31        // -0.0 + 0.0 == +0.0 with IEEE754 roundTiesToEven use by rust
32        (self.0 + 0.0).to_bits().hash(state);
33    }
34}
35
36impl core::hash::Hash for NonNaNFinite<f64> {
37    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
38        // `+0.0` and `-0.0` are equal to they must have the same hash
39        // -0.0 + 0.0 == +0.0 with IEEE754 roundTiesToEven use by rust
40        (self.0 + 0.0).to_bits().hash(state);
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use crate::*;
47
48    #[test]
49    fn zero_optimization() {
50        let zero_f32 = 0.0f32;
51        let zero_f64 = 0.0f64;
52
53        let neg_zero_f32 = -0.0f32;
54        let neg_zero_f64 = -0.0f64;
55
56        let sum_f32 = zero_f32 + neg_zero_f32;
57        let sum_f64 = zero_f64 + neg_zero_f64;
58
59        assert!(sum_f32.is_sign_positive());
60        assert!(sum_f64.is_sign_positive());
61
62        let values = tf32::get_test_values();
63        for value in values {
64            if value != 0.0 && !value.is_nan() {
65                let sum = value + 0.0;
66                assert_eq!(sum, value);
67                assert_eq!(sum.to_bits(), value.to_bits());
68                assert_eq!(sum.is_sign_positive(), value.is_sign_positive());
69            }
70        }
71
72        let values = tf64::get_test_values();
73        for value in values {
74            if value != 0.0 && !value.is_nan() {
75                let sum = value + 0.0;
76                assert_eq!(sum, value);
77                assert_eq!(sum.to_bits(), value.to_bits());
78                assert_eq!(sum.is_sign_positive(), value.is_sign_positive());
79            }
80        }
81    }
82}
83
84macro_rules! impl_hash_test {
85    ($test:ident, $type:ident) => {
86        #[cfg(test)]
87        mod $test {
88            extern crate std;
89            use crate::*;
90            use std::vec::Vec; // Required for the tests to compile in no_std mode
91
92            #[test]
93            fn f32() {
94                let mut hash_set = std::collections::HashSet::new();
95
96                let neg_zero = $type::<f32>::new(-0.0);
97                let pos_zero = $type::<f32>::new(0.0);
98                let accept_both_zeroes = neg_zero.is_ok() && pos_zero.is_ok();
99                if accept_both_zeroes {
100                    let pos_one = $type::<f32>::new(1.0);
101                    let neg_one = $type::<f32>::new(-1.0);
102
103                    hash_set.insert(neg_zero.unwrap());
104                    hash_set.insert(pos_zero.unwrap());
105                    let mut count = 1; // Zeros are equal
106                    assert_eq!(hash_set.len(), count);
107
108                    if pos_one.is_ok() {
109                        hash_set.insert(pos_one.unwrap());
110                        count += 1;
111                    }
112
113                    if neg_one.is_ok() {
114                        hash_set.insert(neg_one.unwrap());
115                        count += 1;
116                    }
117
118                    assert_eq!(hash_set.len(), count);
119                }
120
121                let values = tf32::get_test_values()
122                    .iter()
123                    .map(|&x| tf32::$type::new(x))
124                    .filter_map(|x| x.ok())
125                    .collect::<Vec<_>>();
126
127                let mut distincs = Vec::new();
128                for x in values.iter() {
129                    if !distincs.contains(x) {
130                        distincs.push(*x);
131                    }
132                }
133
134                let mut hash_set = std::collections::HashSet::new();
135
136                for value in &values {
137                    hash_set.insert(value);
138                }
139
140                assert_eq!(hash_set.len(), distincs.len());
141            }
142
143            #[test]
144            fn f64() {
145                let mut hash_set = std::collections::HashSet::new();
146
147                let neg_zero = $type::<f64>::new(-0.0);
148                let pos_zero = $type::<f64>::new(0.0);
149                let accept_both_zeroes = neg_zero.is_ok() && pos_zero.is_ok();
150                if accept_both_zeroes {
151                    let pos_one = $type::<f64>::new(1.0);
152                    let neg_one = $type::<f64>::new(-1.0);
153
154                    hash_set.insert(neg_zero.unwrap());
155                    hash_set.insert(pos_zero.unwrap());
156                    let mut count = 1; // Zeros are equal
157                    assert_eq!(hash_set.len(), count);
158
159                    if pos_one.is_ok() {
160                        hash_set.insert(pos_one.unwrap());
161                        count += 1;
162                    }
163
164                    if neg_one.is_ok() {
165                        hash_set.insert(neg_one.unwrap());
166                        count += 1;
167                    }
168
169                    assert_eq!(hash_set.len(), count);
170                }
171
172                let values = tf64::get_test_values()
173                    .iter()
174                    .map(|&x| tf64::$type::new(x))
175                    .filter_map(|x| x.ok())
176                    .collect::<Vec<_>>();
177
178                let mut distincs = Vec::new();
179                for x in values.iter() {
180                    if !distincs.contains(x) {
181                        distincs.push(*x);
182                    }
183                }
184
185                let mut hash_set = std::collections::HashSet::new();
186
187                for value in &values {
188                    hash_set.insert(value);
189                }
190
191                assert_eq!(hash_set.len(), distincs.len());
192            }
193        }
194    };
195}
196
197macro_rules! impl_hash {
198    ($test:ident, $type:ident) => {
199        impl core::hash::Hash for $type<f32> {
200            #[inline]
201            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
202                self.0.to_bits().hash(state);
203            }
204        }
205
206        impl core::hash::Hash for $type<f64> {
207            #[inline]
208            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
209                self.0.to_bits().hash(state);
210            }
211        }
212
213        impl_hash_test!($test, $type);
214    };
215}
216
217impl_hash!(non_zero_non_nan, NonZeroNonNaN);
218impl_hash!(non_zero_non_nan_finite, NonZeroNonNaNFinite);
219impl_hash!(positive, Positive);
220impl_hash!(negative, Negative);
221impl_hash!(positive_finite, PositiveFinite);
222impl_hash!(negative_finite, NegativeFinite);
223impl_hash!(strictly_positive, StrictlyPositive);
224impl_hash!(strictly_negative, StrictlyNegative);
225impl_hash!(strictly_positive_finite, StrictlyPositiveFinite);
226impl_hash!(strictly_negative_finite, StrictlyNegativeFinite);
227
228impl_hash_test!(non_nan, NonNaN);
229impl_hash_test!(non_nan_finite, NonNaNFinite);