num_order/
hash.rs

1//! We use the Mersenne prime 2^127-1 (i128::MAX) as the main modulo, which maximize the space of available hashing slots.
2//! (The largest Mersenne prime under 2^64 is only 2^61-1, so we use u128 for hashing which is also future proof).
3//!
4//! The basic algorithm is similar to what is used in Python (see https://docs.python.org/3.8/library/stdtypes.html#hashing-of-numeric-types),
5//! specifically if the numerically consistent hash function is denoted as num_hash, then:
6//! - for an integer n: num_hash(n) = sgn(n) * (|n| % M127)
7//! - for a rational number n/d (including floating numbers): sgn(n/d) * num_hash(|n|) * (num_hash(|d|)^-1 mod M127)
8//! - for special values: num_hash(NaN) and num_hash(±∞) are specially chosen such that it won't overlap with normal numbers.
9
10use crate::NumHash;
11
12use core::hash::{Hash, Hasher};
13use num_modular::{FixedMersenneInt, ModularAbs, ModularInteger};
14
15// we use 2^127 - 1 (a Mersenne prime) as modulus
16type MInt = FixedMersenneInt<127, 1>;
17const M127: i128 = i128::MAX;
18const M127U: u128 = M127 as u128;
19const M127D: u128 = M127U + M127U;
20const HASH_INF: i128 = i128::MAX; // 2^127 - 1
21const HASH_NEGINF: i128 = i128::MIN + 1; // -(2^127 - 1)
22const HASH_NAN: i128 = i128::MIN; // -2^127
23
24#[cfg(feature = "num-complex")]
25const PROOT: u128 = i32::MAX as u128; // a Mersenne prime
26
27// TODO (v2.0): Use the coefficients of the characteristic polynomial to represent a number. By this way
28//              all algebraic numbers can be represented including complex and quadratic numbers.
29
30// Case1: directly hash the i128 and u128 number (mod M127)
31impl NumHash for i128 {
32    #[inline]
33    fn num_hash<H: Hasher>(&self, state: &mut H) {
34        const MINP1: i128 = i128::MIN + 1;
35        match *self {
36            i128::MAX | MINP1 => 0i128.hash(state),
37            i128::MIN => (-1i128).hash(state),
38            u => u.hash(state),
39        }
40    }
41}
42impl NumHash for u128 {
43    #[inline]
44    fn num_hash<H: Hasher>(&self, state: &mut H) {
45        match *self {
46            u128::MAX => 1i128.hash(state),
47            M127D => 0i128.hash(state),
48            u if u >= M127U => ((u - M127U) as i128).hash(state),
49            u => (u as i128).hash(state),
50        }
51    }
52}
53
54// Case2: convert other integers to 64 bit integer
55macro_rules! impl_hash_for_small_int {
56    ($($signed:ty)*) => ($(
57        impl NumHash for $signed {
58            #[inline]
59            fn num_hash<H: Hasher>(&self, state: &mut H) {
60                (&(*self as i128)).hash(state) // these integers are always smaller than M127
61            }
62        }
63    )*);
64}
65impl_hash_for_small_int! { i8 i16 i32 i64 u8 u16 u32 u64}
66
67impl NumHash for usize {
68    #[inline]
69    fn num_hash<H: Hasher>(&self, state: &mut H) {
70        #[cfg(target_pointer_width = "32")]
71        return (&(*self as u32)).num_hash(state);
72        #[cfg(target_pointer_width = "64")]
73        return (&(*self as u64)).num_hash(state);
74    }
75}
76
77impl NumHash for isize {
78    #[inline]
79    fn num_hash<H: Hasher>(&self, state: &mut H) {
80        #[cfg(target_pointer_width = "32")]
81        return (&(*self as i32)).num_hash(state);
82        #[cfg(target_pointer_width = "64")]
83        return (&(*self as i64)).num_hash(state);
84    }
85}
86
87#[cfg(feature = "num-bigint")]
88mod _num_bigint {
89    use super::*;
90    use num_bigint::{BigInt, BigUint};
91    use num_traits::ToPrimitive;
92
93    impl NumHash for BigUint {
94        fn num_hash<H: Hasher>(&self, state: &mut H) {
95            (self % BigUint::from(M127U)).to_i128().unwrap().hash(state)
96        }
97    }
98    impl NumHash for BigInt {
99        fn num_hash<H: Hasher>(&self, state: &mut H) {
100            (self % BigInt::from(M127)).to_i128().unwrap().hash(state)
101        }
102    }
103}
104
105// Case3: for rational(a, b) including floating numbers, the hash is `hash(a * b^-1 mod M127)` (b > 0)
106trait FloatHash {
107    // Calculate mantissa * exponent^-1 mod M127
108    fn fhash(&self) -> i128;
109}
110
111impl FloatHash for f32 {
112    fn fhash(&self) -> i128 {
113        let bits = self.to_bits();
114        let sign_bit = bits >> 31;
115        let mantissa_bits = bits & 0x7fffff;
116        let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
117
118        if exponent == 0xff {
119            // deal with special floats
120            if mantissa_bits != 0 {
121                // nan
122                HASH_NAN
123            } else if sign_bit > 0 {
124                HASH_NEGINF // -inf
125            } else {
126                HASH_INF // inf
127            }
128        } else {
129            // then deal with normal floats
130            let mantissa = if exponent == 0 {
131                mantissa_bits << 1
132            } else {
133                mantissa_bits | 0x800000
134            };
135            exponent -= 0x7f + 23;
136
137            // calculate hash
138            let mantissa = MInt::new(mantissa as u128, &M127U);
139            // m * 2^e mod M127 = m * 2^(e mod 127) mod M127
140            let pow = mantissa.convert(1 << exponent.absm(&127));
141            let v = mantissa * pow;
142            v.residue() as i128 * if sign_bit == 0 { 1 } else { -1 }
143        }
144    }
145}
146
147impl NumHash for f32 {
148    fn num_hash<H: Hasher>(&self, state: &mut H) {
149        self.fhash().num_hash(state)
150    }
151}
152
153impl FloatHash for f64 {
154    fn fhash(&self) -> i128 {
155        let bits = self.to_bits();
156        let sign_bit = bits >> 63;
157        let mantissa_bits = bits & 0xfffffffffffff;
158        let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
159
160        if exponent == 0x7ff {
161            // deal with special floats
162            if mantissa_bits != 0 {
163                // nan
164                HASH_NAN
165            } else if sign_bit > 0 {
166                HASH_NEGINF // -inf
167            } else {
168                HASH_INF // inf
169            }
170        } else {
171            // deal with normal floats
172            let mantissa = if exponent == 0 {
173                mantissa_bits << 1
174            } else {
175                mantissa_bits | 0x10000000000000
176            };
177            // Exponent bias + mantissa shift
178            exponent -= 0x3ff + 52;
179
180            // calculate hash
181            let mantissa = MInt::new(mantissa as u128, &M127U);
182            // m * 2^e mod M127 = m * 2^(e mod 127) mod M127
183            let pow = mantissa.convert(1 << exponent.absm(&127));
184            let v = mantissa * pow;
185            v.residue() as i128 * if sign_bit == 0 { 1 } else { -1 }
186        }
187    }
188}
189
190impl NumHash for f64 {
191    fn num_hash<H: Hasher>(&self, state: &mut H) {
192        self.fhash().num_hash(state)
193    }
194}
195
196#[cfg(feature = "num-rational")]
197mod _num_rational {
198    use super::*;
199    use core::ops::Neg;
200    use num_rational::Ratio;
201
202    macro_rules! impl_hash_for_ratio {
203        ($($int:ty)*) => ($(
204            impl NumHash for Ratio<$int> {
205                fn num_hash<H: Hasher>(&self, state: &mut H) {
206                    let ub = *self.denom() as u128; // denom is always positive in Ratio
207                    let binv = if ub != M127U {
208                        MInt::new(ub, &M127U).inv().unwrap()
209                    } else {
210                        // no modular inverse, use INF or NEGINF as the result
211                        return if self.numer() > &0 { HASH_INF.num_hash(state) } else { HASH_NEGINF.num_hash(state) }
212                    };
213
214                    let ua = if self.numer() < &0 { (*self.numer() as u128).wrapping_neg() } else { *self.numer() as u128 }; // essentially calculate |self.numer()|
215                    let ua = binv.convert(ua);
216                    let ab = (ua * binv).residue() as i128;
217                    if self.numer() >= &0 {
218                        ab.num_hash(state)
219                    } else {
220                        ab.neg().num_hash(state)
221                    }
222                }
223            }
224        )*);
225    }
226
227    impl_hash_for_ratio!(i8 i16 i32 i64 i128 isize);
228
229    #[cfg(feature = "num-bigint")]
230    mod _num_bigint {
231        use super::*;
232        use num_bigint::{BigInt, BigUint};
233        use num_traits::{Signed, ToPrimitive, Zero};
234
235        impl NumHash for Ratio<BigInt> {
236            fn num_hash<H: Hasher>(&self, state: &mut H) {
237                let ub = (self.denom().magnitude() % BigUint::from(M127U))
238                    .to_u128()
239                    .unwrap();
240                let binv = if !ub.is_zero() {
241                    MInt::new(ub, &M127U).inv().unwrap()
242                } else {
243                    // no modular inverse, use INF or NEGINF as the result
244                    return if self.numer().is_negative() {
245                        HASH_NEGINF.num_hash(state)
246                    } else {
247                        HASH_INF.num_hash(state)
248                    };
249                };
250
251                let ua = (self.numer().magnitude() % BigUint::from(M127U))
252                    .to_u128()
253                    .unwrap();
254                let ua = binv.convert(ua);
255                let ab = (ua * binv).residue() as i128;
256                if self.numer().is_negative() {
257                    ab.neg().num_hash(state)
258                } else {
259                    ab.num_hash(state)
260                }
261            }
262        }
263    }
264}
265
266// Case4: for a + b*sqrt(r) where a, b are rational numbers, the hash is
267// - `hash(a + PROOT^2*b^2*r)` if b > 0
268// - `hash(a - PROOT^2*b^2*r)` if b < 0
269// The generalized version is that, hash of (a + b*r^(1/k)) will be `hash(a + PROOT^k*b^k*r)`
270// Some Caveats:
271// 1. if r = 1, the hash is not consistent with normal integer, but r = 1 is forbidden in QuadraticSurd
272// 2. a - b*sqrt(r) and a + b*sqrt(-r) has the same hash, which is usually not a problem
273#[cfg(feature = "num-complex")]
274mod _num_complex {
275    use super::*;
276    use num_complex::Complex;
277
278    macro_rules! impl_complex_hash_for_float {
279        ($($float:ty)*) => ($(
280            impl NumHash for Complex<$float> {
281                fn num_hash<H: Hasher>(&self, state: &mut H) {
282                    let a = self.re.fhash();
283                    let b = self.im.fhash();
284
285                    let bterm = if b >= 0 {
286                        let pb = MInt::new(b as u128, &M127U) * PROOT;
287                        -((pb * pb).residue() as i128)
288                    } else {
289                        let pb = MInt::new((-b) as u128, &M127U) * PROOT;
290                        (pb * pb).residue() as i128
291                    };
292                    (a + bterm).num_hash(state)
293                }
294            }
295        )*);
296    }
297    impl_complex_hash_for_float!(f32 f64);
298}