dashu_float/third_party/
num_order.rs

1use _num_modular::{FixedMersenneInt, ModularAbs, ModularInteger};
2use core::cmp::Ordering;
3use dashu_base::{BitTest, EstimatedLog2, FloatEncoding, Sign, Signed};
4use dashu_int::{IBig, UBig, Word};
5use num_order::{NumHash, NumOrd};
6
7use crate::{
8    cmp::{repr_cmp_ibig, repr_cmp_ubig},
9    fbig::FBig,
10    repr::Repr,
11    round::Round,
12    utils::shl_digits_in_place,
13};
14
15impl<const B1: Word, const B2: Word> NumOrd<Repr<B2>> for Repr<B1> {
16    fn num_cmp(&self, other: &Repr<B2>) -> Ordering {
17        // case 1: compare with inf
18        match (self.is_infinite(), other.is_infinite()) {
19            (true, true) => return self.exponent.cmp(&other.exponent),
20            (false, true) => {
21                return match other.exponent >= 0 {
22                    true => Ordering::Less,
23                    false => Ordering::Greater,
24                }
25            }
26            (true, false) => {
27                return match self.exponent >= 0 {
28                    true => Ordering::Greater,
29                    false => Ordering::Less,
30                }
31            }
32            _ => {}
33        };
34
35        // case 2: compare sign
36        let sign = match (self.significand.sign(), other.significand.sign()) {
37            (Sign::Positive, Sign::Positive) => Sign::Positive,
38            (Sign::Positive, Sign::Negative) => return Ordering::Greater,
39            (Sign::Negative, Sign::Positive) => return Ordering::Less,
40            (Sign::Negative, Sign::Negative) => Sign::Negative,
41        };
42
43        // case 3: compare log2 estimations
44        let (self_lo, self_hi) = self.log2_bounds();
45        let (other_lo, other_hi) = other.log2_bounds();
46        if self_lo > other_hi {
47            return sign * Ordering::Greater;
48        }
49        if self_hi < other_lo {
50            return sign * Ordering::Less;
51        }
52
53        // case 4: compare the exact values
54        let (mut lhs, mut rhs) = (self.significand.clone(), other.significand.clone());
55        if self.exponent < 0 {
56            shl_digits_in_place::<B1>(&mut rhs, (-self.exponent) as usize);
57        } else {
58            shl_digits_in_place::<B1>(&mut lhs, self.exponent as usize);
59        }
60        if other.exponent < 0 {
61            shl_digits_in_place::<B2>(&mut lhs, (-other.exponent) as usize);
62        } else {
63            shl_digits_in_place::<B2>(&mut rhs, other.exponent as usize);
64        }
65        lhs.cmp(&rhs)
66    }
67    #[inline]
68    fn num_partial_cmp(&self, other: &Repr<B2>) -> Option<Ordering> {
69        Some(self.num_cmp(other))
70    }
71}
72
73impl<R1: Round, R2: Round, const B1: Word, const B2: Word> NumOrd<FBig<R2, B2>> for FBig<R1, B1> {
74    #[inline]
75    fn num_cmp(&self, other: &FBig<R2, B2>) -> Ordering {
76        self.repr.num_cmp(&other.repr)
77    }
78    #[inline]
79    fn num_partial_cmp(&self, other: &FBig<R2, B2>) -> Option<Ordering> {
80        self.repr.num_partial_cmp(&other.repr)
81    }
82}
83
84macro_rules! impl_num_ord_with_method {
85    ($T:ty, $method:ident) => {
86        impl<const B: Word> NumOrd<$T> for Repr<B> {
87            #[inline]
88            fn num_cmp(&self, other: &$T) -> Ordering {
89                $method::<B, false>(self, other)
90            }
91            #[inline]
92            fn num_partial_cmp(&self, other: &$T) -> Option<Ordering> {
93                Some($method::<B, false>(self, other))
94            }
95        }
96        impl<const B: Word> NumOrd<Repr<B>> for $T {
97            #[inline]
98            fn num_cmp(&self, other: &Repr<B>) -> Ordering {
99                $method::<B, false>(other, self).reverse()
100            }
101            #[inline]
102            fn num_partial_cmp(&self, other: &Repr<B>) -> Option<Ordering> {
103                Some($method::<B, false>(other, self).reverse())
104            }
105        }
106    };
107}
108impl_num_ord_with_method!(UBig, repr_cmp_ubig);
109impl_num_ord_with_method!(IBig, repr_cmp_ibig);
110
111macro_rules! forward_num_ord_to_repr {
112    ($t:ty) => {
113        impl<R: Round, const B: Word> NumOrd<$t> for FBig<R, B> {
114            #[inline]
115            fn num_cmp(&self, other: &$t) -> Ordering {
116                self.repr.num_cmp(other)
117            }
118            #[inline]
119            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
120                self.repr.num_partial_cmp(other)
121            }
122        }
123
124        impl<R: Round, const B: Word> NumOrd<FBig<R, B>> for $t {
125            #[inline]
126            fn num_cmp(&self, other: &FBig<R, B>) -> Ordering {
127                self.num_cmp(&other.repr)
128            }
129            #[inline]
130            fn num_partial_cmp(&self, other: &FBig<R, B>) -> Option<Ordering> {
131                self.num_partial_cmp(&other.repr)
132            }
133        }
134    };
135}
136forward_num_ord_to_repr!(UBig);
137forward_num_ord_to_repr!(IBig);
138
139macro_rules! impl_num_ord_fbig_unsigned {
140    ($($t:ty)*) => {$(
141        impl<const B: Word> NumOrd<$t> for Repr<B> {
142            #[inline]
143            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
144                Some(repr_cmp_ubig::<B, false>(self, &UBig::from(*other)))
145            }
146        }
147        impl<const B: Word> NumOrd<Repr<B>> for $t {
148            #[inline]
149            fn num_partial_cmp(&self, other: &Repr<B>) -> Option<Ordering> {
150                Some(repr_cmp_ubig::<B, false>(other, &UBig::from(*self)).reverse())
151            }
152        }
153        impl<R: Round, const B: Word> NumOrd<$t> for FBig<R, B> {
154            #[inline]
155            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
156                Some(repr_cmp_ubig::<B, false>(&self.repr, &UBig::from(*other)))
157            }
158        }
159        impl<R: Round, const B: Word> NumOrd<FBig<R, B>> for $t {
160            #[inline]
161            fn num_partial_cmp(&self, other: &FBig<R, B>) -> Option<Ordering> {
162                Some(repr_cmp_ubig::<B, false>(&other.repr, &UBig::from(*self)).reverse())
163            }
164        }
165    )*};
166}
167impl_num_ord_fbig_unsigned!(u8 u16 u32 u64 u128 usize);
168
169macro_rules! impl_num_ord_with_signed {
170    ($($t:ty)*) => {$(
171        impl<const B: Word> NumOrd<$t> for Repr<B> {
172            #[inline]
173            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
174                Some(repr_cmp_ibig::<B, false>(self, &IBig::from(*other)))
175            }
176        }
177        impl<const B: Word> NumOrd<Repr<B>> for $t {
178            #[inline]
179            fn num_partial_cmp(&self, other: &Repr<B>) -> Option<Ordering> {
180                Some(repr_cmp_ibig::<B, false>(other, &IBig::from(*self)).reverse())
181            }
182        }
183        impl<R: Round, const B: Word> NumOrd<$t> for FBig<R, B> {
184            #[inline]
185            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
186                Some(repr_cmp_ibig::<B, false>(&self.repr, &IBig::from(*other)))
187            }
188        }
189        impl<R: Round, const B: Word> NumOrd<FBig<R, B>> for $t {
190            #[inline]
191            fn num_partial_cmp(&self, other: &FBig<R, B>) -> Option<Ordering> {
192                Some(repr_cmp_ibig::<B, false>(&other.repr, &IBig::from(*self)).reverse())
193            }
194        }
195    )*};
196}
197impl_num_ord_with_signed!(i8 i16 i32 i64 i128 isize);
198
199macro_rules! impl_num_ord_with_float {
200    ($($t:ty)*) => {$(
201        impl<const B: Word> NumOrd<$t> for Repr<B> {
202            #[inline]
203            fn num_partial_cmp(&self, other: &$t) -> Option<Ordering> {
204                // step0: compare with nan and 0
205                if other.is_nan() {
206                    return None;
207                } else if *other == 0. {
208                    return match self.is_zero() {
209                        true => Some(Ordering::Equal),
210                        false => Some(self.sign() * Ordering::Greater)
211                    };
212                }
213
214                // step1: compare sign
215                let sign = match (self.sign(), other.sign()) {
216                    (Sign::Positive, Sign::Positive) => Sign::Positive,
217                    (Sign::Positive, Sign::Negative) => return Some(Ordering::Greater),
218                    (Sign::Negative, Sign::Positive) => return Some(Ordering::Less),
219                    (Sign::Negative, Sign::Negative) => Sign::Negative,
220                };
221
222                // step2: compare with inf
223                match (self.is_infinite(), other.is_infinite()) {
224                    (true, true) => return Some(Ordering::Equal),
225                    (false, true) => return Some(sign * Ordering::Less),
226                    (true, false) => return Some(sign * Ordering::Greater),
227                    _ => {}
228                };
229
230                // step3: test if the number is bigger than the max float value
231                // Here we don't use EstimatedLog2, since a direct comparison is not that expensive.
232                // We just need a quick way to determine if one number is much larger than the other.
233                // The bit length (essentially ⌊log2(x)⌋ + 1) is used instead here.
234                let self_signif_log2 = self.significand.bit_len() as isize;
235                let self_log2 = self_signif_log2 + B.bit_len() as isize * self.exponent;
236                let (self_log2_lb, self_log2_ub) = if self.exponent >= 0 {
237                    (self_log2 - self.exponent, self_log2)
238                } else {
239                    (self_log2, self_log2 - self.exponent)
240                };
241                if self_log2_lb > (<$t>::MANTISSA_DIGITS as isize + <$t>::MAX_EXP as isize) {
242                    return Some(sign * Ordering::Greater);
243                }
244
245                // step4: decode the float and compare the bits
246                let (other_signif, other_exp) = other.decode().unwrap();
247                let other_log2 = other_signif.bit_len() as isize + other_exp as isize;
248                if self_log2_lb > other_log2 {
249                    return Some(sign * Ordering::Greater);
250                } else if self_log2_ub < other_log2 {
251                    return Some(sign * Ordering::Less);
252                }
253
254                // step5: do the final comparison
255                let (mut lhs, mut rhs) = (self.significand.clone(), IBig::from(other_signif));
256                if self.exponent < 0 {
257                    shl_digits_in_place::<B>(&mut rhs, (-self.exponent) as usize);
258                } else {
259                    shl_digits_in_place::<B>(&mut lhs, self.exponent as usize);
260                }
261                if other_exp < 0 {
262                    lhs <<= (-other_exp) as usize;
263                } else {
264                    rhs <<= other_exp as usize;
265                }
266                Some(lhs.cmp(&rhs))
267            }
268        }
269
270        impl<const B: Word> NumOrd<Repr<B>> for $t {
271            #[inline]
272            fn num_partial_cmp(&self, other: &Repr<B>) -> Option<Ordering> {
273                other.num_partial_cmp(self).map(|ord| ord.reverse())
274            }
275        }
276    )*};
277}
278impl_num_ord_with_float!(f32 f64);
279forward_num_ord_to_repr!(f32);
280forward_num_ord_to_repr!(f64);
281
282impl<const B: Word> NumHash for Repr<B> {
283    fn num_hash<H: core::hash::Hasher>(&self, state: &mut H) {
284        // 2^127 - 1 is used in the num-order crate
285        type MInt = FixedMersenneInt<127, 1>;
286        const M127: i128 = i128::MAX;
287        const M127U: u128 = M127 as u128;
288
289        let signif_residue = &self.significand % M127;
290        let signif_hash = MInt::new(signif_residue.unsigned_abs(), &M127U);
291        let exp_hash = if B == 2 {
292            signif_hash.convert(1 << self.exponent.absm(&127))
293        } else if self.exponent < 0 {
294            // since a Word is at most 64 bits right now, B is always less than M127
295            signif_hash
296                .convert(B as u128)
297                .pow(&(-self.exponent as u128))
298                .inv()
299                .unwrap()
300        } else {
301            signif_hash.convert(B as u128).pow(&(self.exponent as u128))
302        };
303
304        let mut hash = (signif_hash * exp_hash).residue() as i128;
305        if signif_residue < 0 {
306            hash = -hash;
307        }
308
309        hash.num_hash(state)
310    }
311}
312
313impl<R: Round, const B: Word> NumHash for FBig<R, B> {
314    #[inline]
315    fn num_hash<H: core::hash::Hasher>(&self, state: &mut H) {
316        self.repr.num_hash(state)
317    }
318}