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 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 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 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 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 if other.is_nan() {
206 return None;
207 } else if *other == 0. {
208 return if self.is_pos_zero() || self.is_neg_zero() {
212 Some(Ordering::Equal)
213 } else {
214 Some(self.sign() * Ordering::Greater)
215 };
216 }
217
218 let sign = match (self.sign(), other.sign()) {
220 (Sign::Positive, Sign::Positive) => Sign::Positive,
221 (Sign::Positive, Sign::Negative) => return Some(Ordering::Greater),
222 (Sign::Negative, Sign::Positive) => return Some(Ordering::Less),
223 (Sign::Negative, Sign::Negative) => Sign::Negative,
224 };
225
226 match (self.is_infinite(), other.is_infinite()) {
228 (true, true) => return Some(Ordering::Equal),
229 (false, true) => return Some(sign * Ordering::Less),
230 (true, false) => return Some(sign * Ordering::Greater),
231 _ => {}
232 };
233
234 let self_signif_log2 = self.significand.bit_len() as isize;
239 let self_log2 = self_signif_log2 + B.bit_len() as isize * self.exponent;
240 let (self_log2_lb, self_log2_ub) = if self.exponent >= 0 {
241 (self_log2 - self.exponent, self_log2)
242 } else {
243 (self_log2, self_log2 - self.exponent)
244 };
245 if self_log2_lb > (<$t>::MANTISSA_DIGITS as isize + <$t>::MAX_EXP as isize) {
246 return Some(sign * Ordering::Greater);
247 }
248
249 let (other_signif, other_exp) = other.decode().unwrap();
251 let other_log2 = other_signif.bit_len() as isize + other_exp as isize;
252 if self_log2_lb > other_log2 {
253 return Some(sign * Ordering::Greater);
254 } else if self_log2_ub < other_log2 {
255 return Some(sign * Ordering::Less);
256 }
257
258 let (mut lhs, mut rhs) = (self.significand.clone(), IBig::from(other_signif));
260 if self.exponent < 0 {
261 shl_digits_in_place::<B>(&mut rhs, (-self.exponent) as usize);
262 } else {
263 shl_digits_in_place::<B>(&mut lhs, self.exponent as usize);
264 }
265 if other_exp < 0 {
266 lhs <<= (-other_exp) as usize;
267 } else {
268 rhs <<= other_exp as usize;
269 }
270 Some(lhs.cmp(&rhs))
271 }
272 }
273
274 impl<const B: Word> NumOrd<Repr<B>> for $t {
275 #[inline]
276 fn num_partial_cmp(&self, other: &Repr<B>) -> Option<Ordering> {
277 other.num_partial_cmp(self).map(|ord| ord.reverse())
278 }
279 }
280 )*};
281}
282impl_num_ord_with_float!(f32 f64);
283forward_num_ord_to_repr!(f32);
284forward_num_ord_to_repr!(f64);
285
286impl<const B: Word> Repr<B> {
287 pub fn num_hash_residue(&self) -> i128 {
296 type MInt = FixedMersenneInt<127, 1>;
298 const M127: i128 = i128::MAX;
299 const M127U: u128 = M127 as u128;
300
301 if self.significand.is_zero() {
302 return match self.exponent {
304 isize::MAX => M127, isize::MIN => i128::MIN + 1, _ => 0, };
308 }
309
310 let signif_residue = &self.significand % M127;
311 let signif_hash = MInt::new(signif_residue.unsigned_abs(), &M127U);
312 let exp_hash = if B == 2 {
313 signif_hash.convert(1 << self.exponent.absm(&127))
314 } else if self.exponent < 0 {
315 signif_hash
316 .convert(B as u128)
317 .pow(&(-self.exponent as u128))
318 .inv()
319 .unwrap()
320 } else {
321 signif_hash.convert(B as u128).pow(&(self.exponent as u128))
322 };
323
324 let mut hash = (signif_hash * exp_hash).residue() as i128;
325 if signif_residue < 0 {
326 hash = -hash;
327 }
328 hash
329 }
330}
331
332impl<const B: Word> NumHash for Repr<B> {
333 #[inline]
334 fn num_hash<H: core::hash::Hasher>(&self, state: &mut H) {
335 self.num_hash_residue().num_hash(state)
336 }
337}
338
339impl<R: Round, const B: Word> NumHash for FBig<R, B> {
340 #[inline]
341 fn num_hash<H: core::hash::Hasher>(&self, state: &mut H) {
342 self.repr.num_hash(state)
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::DBig;
350 use core::cmp::Ordering;
351 use num_order::{NumHash, NumOrd};
352
353 type FBin = FBig;
355
356 fn num_hash<T: NumHash>(value: &T) -> u64 {
358 use std::collections::hash_map::DefaultHasher;
359 use std::hash::Hasher;
360 let mut hasher = DefaultHasher::new();
361 value.num_hash(&mut hasher);
362 hasher.finish()
363 }
364
365 fn residue<T: NumHash>(value: &T) -> i128 {
368 struct Collector(i128);
369 impl core::hash::Hasher for Collector {
370 fn write_i128(&mut self, v: i128) {
371 self.0 = v;
372 }
373 fn write(&mut self, _: &[u8]) {}
374 fn finish(&self) -> u64 {
375 0
376 }
377 }
378 let mut c = Collector(0);
379 value.num_hash(&mut c);
380 c.0
381 }
382
383 #[test]
387 fn test_fbig_num_hash_matches_f64() {
388 for v in [
389 1.0_f64,
390 2.0,
391 3.0,
392 0.5,
393 0.25,
394 -0.75,
395 100.0,
396 1e-10,
397 1e20,
398 123.456,
399 1.0 / 3.0,
400 f64::INFINITY,
401 f64::NEG_INFINITY,
402 -0.0,
403 ] {
404 let f: FBin = core::convert::TryFrom::try_from(v).unwrap();
405 assert_eq!(residue(&f), residue(&v), "FBig/f64 num_hash disagree for {v}");
406 }
407 }
408
409 #[test]
412 fn test_num_ord_repr_zero_vs_neg_zero() {
413 assert_eq!(Repr::<2>::zero().num_cmp(&Repr::<2>::neg_zero()), Ordering::Equal);
415 assert_eq!(Repr::<2>::neg_zero().num_cmp(&Repr::<2>::zero()), Ordering::Equal);
416 }
417
418 #[test]
419 fn test_num_ord_repr_neg_zero_vs_finite() {
420 let one = Repr::<2>::one();
421 let neg_one = Repr::<2>::neg_one();
422 assert_eq!(Repr::<2>::neg_zero().num_cmp(&one), Ordering::Less);
424 assert_eq!(Repr::<2>::neg_zero().num_cmp(&neg_one), Ordering::Greater);
426 }
427
428 #[test]
429 fn test_num_ord_repr_infinities() {
430 assert_eq!(Repr::<2>::infinity().num_cmp(&Repr::<2>::neg_infinity()), Ordering::Greater);
432 assert_eq!(Repr::<2>::neg_infinity().num_cmp(&Repr::<2>::infinity()), Ordering::Less);
434 assert_eq!(Repr::<2>::infinity().num_cmp(&Repr::<2>::infinity()), Ordering::Equal);
436 assert_eq!(Repr::<2>::neg_infinity().num_cmp(&Repr::<2>::neg_infinity()), Ordering::Equal);
438 }
439
440 #[test]
441 fn test_num_ord_repr_zero_vs_infinity() {
442 assert_eq!(Repr::<2>::zero().num_cmp(&Repr::<2>::infinity()), Ordering::Less);
444 assert_eq!(Repr::<2>::neg_zero().num_cmp(&Repr::<2>::infinity()), Ordering::Less);
446 assert_eq!(Repr::<2>::zero().num_cmp(&Repr::<2>::neg_infinity()), Ordering::Greater);
448 assert_eq!(Repr::<2>::neg_zero().num_cmp(&Repr::<2>::neg_infinity()), Ordering::Greater);
450 }
451
452 #[test]
455 fn test_num_ord_repr_cross_base_zero() {
456 assert_eq!(Repr::<2>::neg_zero().num_cmp(&Repr::<10>::zero()), Ordering::Equal);
458 assert_eq!(Repr::<2>::neg_zero().num_cmp(&Repr::<10>::neg_zero()), Ordering::Equal);
460 }
461
462 #[test]
463 fn test_num_ord_repr_cross_base_infinity() {
464 assert_eq!(Repr::<2>::infinity().num_cmp(&Repr::<10>::infinity()), Ordering::Equal);
466 assert_eq!(Repr::<2>::infinity().num_cmp(&Repr::<10>::neg_infinity()), Ordering::Greater);
468 assert_eq!(Repr::<2>::neg_infinity().num_cmp(&Repr::<10>::neg_infinity()), Ordering::Equal);
470 }
471
472 #[test]
475 fn test_num_ord_fbig_neg_zero() {
476 let negz: FBin = FBig::from_repr_const(Repr::<2>::neg_zero());
477 let posz = FBin::ZERO;
478 assert_eq!(negz.num_cmp(&posz), Ordering::Equal);
479 assert_eq!(posz.num_cmp(&negz), Ordering::Equal);
480
481 assert_eq!(negz.num_cmp(&FBin::ONE), Ordering::Less);
483 assert_eq!(negz.num_cmp(&FBin::NEG_ONE), Ordering::Greater);
484 }
485
486 #[test]
487 fn test_num_ord_fbig_cross_base_zero() {
488 let negz: FBin = FBig::from_repr_const(Repr::<2>::neg_zero());
489 assert_eq!(negz.num_cmp(&DBig::ZERO), Ordering::Equal);
490 assert_eq!(DBig::ZERO.num_cmp(&negz), Ordering::Equal);
491 }
492
493 #[test]
496 fn test_num_hash_repr_zero_neg_zero_equal() {
497 assert_eq!(num_hash(&Repr::<2>::zero()), num_hash(&Repr::<2>::neg_zero()));
499 assert_eq!(num_hash(&Repr::<10>::zero()), num_hash(&Repr::<10>::neg_zero()));
500 }
501
502 #[test]
503 fn test_num_hash_repr_infinities_same_sign() {
504 assert_eq!(num_hash(&Repr::<2>::infinity()), num_hash(&Repr::<10>::infinity()));
506 assert_eq!(num_hash(&Repr::<2>::neg_infinity()), num_hash(&Repr::<10>::neg_infinity()));
507 }
508
509 #[test]
510 fn test_num_hash_repr_zero_matches_integer_zero() {
511 assert_eq!(num_hash(&Repr::<2>::zero()), num_hash(&0i128));
513 assert_eq!(num_hash(&Repr::<2>::neg_zero()), num_hash(&0i128));
514 }
515
516 #[test]
519 fn test_num_hash_fbig_neg_zero() {
520 let negz: FBin = FBig::from_repr_const(Repr::<2>::neg_zero());
521 assert_eq!(num_hash(&negz), num_hash(&FBin::ZERO));
522 }
523
524 #[test]
525 fn test_num_hash_fbig_cross_base_zero() {
526 let negz: FBin = FBig::from_repr_const(Repr::<2>::neg_zero());
527 assert_eq!(num_hash(&negz), num_hash(&DBig::ZERO));
528 }
529}