dashu_ratio/
cmp.rs

1#![allow(deprecated)] // TODO(v0.5): remove after the implementations for AbsEq are removed.
2
3use crate::{repr::Repr, RBig, Relaxed};
4use core::{
5    cmp::Ordering,
6    hash::{Hash, Hasher},
7};
8use dashu_base::{
9    AbsEq, AbsOrd, BitTest, EstimatedLog2,
10    Sign::{self, *},
11};
12use dashu_int::{IBig, UBig};
13
14/// Check whether a == b. `ABS` determine whether the signs are ignored during comparison
15fn repr_eq<const ABS: bool>(a: &Repr, b: &Repr) -> bool {
16    // for relaxed representation, we have to compare it's actual value
17    if !ABS && a.numerator.sign() != b.numerator.sign() {
18        return false;
19    }
20    if a.numerator.is_zero() {
21        return b.numerator.is_zero();
22    }
23
24    let n1d2_bits = a.numerator.bit_len() as isize + b.denominator.bit_len() as isize;
25    let n2d1_bits = b.numerator.bit_len() as isize + a.denominator.bit_len() as isize;
26    if n1d2_bits.abs_diff(n2d1_bits) > 1 {
27        return false;
28    }
29
30    // do the final product after filtering out simple cases
31    let lhs = &a.numerator * &b.denominator;
32    let rhs = &b.numerator * &a.denominator;
33    lhs.abs_eq(&rhs)
34}
35
36impl PartialEq for Repr {
37    #[inline]
38    fn eq(&self, other: &Self) -> bool {
39        repr_eq::<false>(self, other)
40    }
41}
42impl Eq for Repr {}
43
44impl AbsEq for Repr {
45    #[inline]
46    fn abs_eq(&self, other: &Self) -> bool {
47        repr_eq::<true>(self, other)
48    }
49}
50
51impl AbsEq for Relaxed {
52    #[inline]
53    fn abs_eq(&self, other: &Self) -> bool {
54        self.0.abs_eq(&other.0)
55    }
56}
57
58impl PartialEq for RBig {
59    #[inline]
60    fn eq(&self, other: &Self) -> bool {
61        // representation of RBig is canonicalized, so it suffices to compare the components
62        self.0.numerator == other.0.numerator && self.0.denominator == other.0.denominator
63    }
64}
65impl Eq for RBig {}
66
67impl AbsEq for RBig {
68    #[inline]
69    fn abs_eq(&self, other: &Self) -> bool {
70        // representation of RBig is canonicalized, so it suffices to compare the components
71        self.0.numerator.abs_eq(&other.0.numerator) && self.0.denominator == other.0.denominator
72    }
73}
74
75// Hash is only implemented for RBig but not for Relaxed, because the representation
76// is not unique for Relaxed.
77impl Hash for RBig {
78    #[inline]
79    fn hash<H: Hasher>(&self, state: &mut H) {
80        self.0.numerator.hash(state);
81        self.0.denominator.hash(state);
82    }
83}
84
85fn repr_cmp<const ABS: bool>(lhs: &Repr, rhs: &Repr) -> Ordering {
86    // step1: compare sign
87    let negative = if ABS {
88        false
89    } else {
90        match (lhs.numerator.sign(), rhs.numerator.sign()) {
91            (Positive, Positive) => false,
92            (Positive, Negative) => return Ordering::Greater,
93            (Negative, Positive) => return Ordering::Less,
94            (Negative, Negative) => true,
95        }
96    };
97
98    // step2: if both numbers are integers or one of them is zero
99    if lhs.denominator.is_one() && rhs.denominator.is_one() {
100        return if ABS {
101            lhs.numerator.abs_cmp(&rhs.numerator)
102        } else {
103            lhs.numerator.cmp(&rhs.numerator)
104        };
105    }
106    match (lhs.numerator.is_zero(), rhs.numerator.is_zero()) {
107        (true, true) => return Ordering::Equal,
108        (true, false) => return Ordering::Less, // `b` must be strictly positive
109        (false, true) => return Ordering::Greater, // `a` must be strictly positive
110        _ => {}
111    };
112
113    // step3: test bit size
114    let lhs_bits = lhs.numerator.bit_len() as isize - lhs.denominator.bit_len() as isize;
115    let rhs_bits = rhs.numerator.bit_len() as isize - rhs.denominator.bit_len() as isize;
116    if lhs_bits > rhs_bits + 1 {
117        return match negative {
118            false => Ordering::Greater,
119            true => Ordering::Less,
120        };
121    } else if rhs_bits < lhs_bits - 1 {
122        return match negative {
123            false => Ordering::Less,
124            true => Ordering::Greater,
125        };
126    }
127
128    // step4: finally do multiplication test
129    let n1d2 = (&lhs.numerator) * (&rhs.denominator);
130    let n2d1 = (&rhs.numerator) * (&lhs.denominator);
131    if ABS {
132        n1d2.abs_cmp(&n2d1)
133    } else {
134        n1d2.cmp(&n2d1)
135    }
136}
137
138impl PartialOrd for Repr {
139    #[inline]
140    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
141        Some(self.cmp(other))
142    }
143}
144
145impl Ord for Repr {
146    #[inline]
147    fn cmp(&self, other: &Self) -> Ordering {
148        repr_cmp::<false>(self, other)
149    }
150}
151
152impl AbsOrd for Repr {
153    #[inline]
154    fn abs_cmp(&self, other: &Self) -> Ordering {
155        repr_cmp::<true>(self, other)
156    }
157}
158
159macro_rules! forward_abs_ord_both_to_repr {
160    ($t1:ty, $t2:ty) => {
161        impl AbsOrd<$t2> for $t1 {
162            #[inline]
163            fn abs_cmp(&self, other: &$t2) -> Ordering {
164                repr_cmp::<true>(&self.0, &other.0)
165            }
166        }
167    };
168}
169forward_abs_ord_both_to_repr!(RBig, RBig);
170forward_abs_ord_both_to_repr!(RBig, Relaxed);
171forward_abs_ord_both_to_repr!(Relaxed, RBig);
172forward_abs_ord_both_to_repr!(Relaxed, Relaxed);
173
174macro_rules! forward_abs_ord_to_repr {
175    ($R:ty, $T:ty) => {
176        impl AbsOrd<$T> for $R {
177            #[inline]
178            fn abs_cmp(&self, other: &$T) -> Ordering {
179                self.0.abs_cmp(other)
180            }
181        }
182        impl AbsOrd<$R> for $T {
183            #[inline]
184            fn abs_cmp(&self, other: &$R) -> Ordering {
185                other.0.abs_cmp(self).reverse()
186            }
187        }
188    };
189}
190
191pub(crate) fn repr_cmp_ubig<const ABS: bool>(lhs: &Repr, rhs: &UBig) -> Ordering {
192    // case 1: compare sign
193    if !ABS && lhs.numerator.sign() == Sign::Negative {
194        return Ordering::Less;
195    }
196
197    // case 2: compare log2 estimations
198    let (lhs_lo, lhs_hi) = lhs.log2_bounds();
199    let (rhs_lo, rhs_hi) = rhs.log2_bounds();
200    if lhs_lo > rhs_hi {
201        return Ordering::Greater;
202    }
203    if lhs_hi < rhs_lo {
204        return Ordering::Less;
205    }
206
207    // case 3: compare the exact values
208    lhs.numerator.abs_cmp(&(rhs * &lhs.denominator))
209}
210
211impl AbsOrd<UBig> for Repr {
212    #[inline]
213    fn abs_cmp(&self, other: &UBig) -> Ordering {
214        repr_cmp_ubig::<true>(self, other)
215    }
216}
217forward_abs_ord_to_repr!(RBig, UBig);
218forward_abs_ord_to_repr!(Relaxed, UBig);
219
220pub(crate) fn repr_cmp_ibig<const ABS: bool>(lhs: &Repr, rhs: &IBig) -> Ordering {
221    // case 1: compare sign
222    let sign = if ABS {
223        Sign::Positive
224    } else {
225        match (lhs.numerator.sign(), rhs.sign()) {
226            (Sign::Positive, Sign::Positive) => Sign::Positive,
227            (Sign::Positive, Sign::Negative) => return Ordering::Greater,
228            (Sign::Negative, Sign::Positive) => return Ordering::Less,
229            (Sign::Negative, Sign::Negative) => Sign::Negative,
230        }
231    };
232
233    // case 2: compare log2 estimations
234    let (lhs_lo, lhs_hi) = lhs.log2_bounds();
235    let (rhs_lo, rhs_hi) = rhs.log2_bounds();
236    if lhs_lo > rhs_hi {
237        return sign * Ordering::Greater;
238    }
239    if lhs_hi < rhs_lo {
240        return sign * Ordering::Less;
241    }
242
243    // case 3: compare the exact values
244    if ABS {
245        lhs.numerator.abs_cmp(&(rhs * &lhs.denominator))
246    } else {
247        lhs.numerator.cmp(&(rhs * &lhs.denominator))
248    }
249}
250
251impl AbsOrd<IBig> for Repr {
252    #[inline]
253    fn abs_cmp(&self, other: &IBig) -> Ordering {
254        repr_cmp_ibig::<true>(self, other)
255    }
256}
257forward_abs_ord_to_repr!(RBig, IBig);
258forward_abs_ord_to_repr!(Relaxed, IBig);
259
260#[cfg(feature = "dashu-float")]
261pub(crate) mod with_float {
262    use super::*;
263    use dashu_float::{round::Round, FBig, Repr as FloatRepr};
264    use dashu_int::Word;
265
266    pub(crate) fn repr_cmp_fbig<const B: Word, const ABS: bool>(
267        lhs: &Repr,
268        rhs: &FloatRepr<B>,
269    ) -> Ordering {
270        // case 1: compare with inf
271        if rhs.is_infinite() {
272            return match ABS || rhs.exponent() > 0 {
273                true => Ordering::Less,
274                false => Ordering::Greater,
275            };
276        }
277
278        // case 2: compare sign
279        let sign = if ABS {
280            Sign::Positive
281        } else {
282            match (lhs.numerator.sign(), rhs.significand().sign()) {
283                (Sign::Positive, Sign::Positive) => Sign::Positive,
284                (Sign::Positive, Sign::Negative) => return Ordering::Greater,
285                (Sign::Negative, Sign::Positive) => return Ordering::Less,
286                (Sign::Negative, Sign::Negative) => Sign::Negative,
287            }
288        };
289
290        // case 3: compare log2 estimations
291        let (lhs_lo, lhs_hi) = lhs.log2_bounds();
292        let (rhs_lo, rhs_hi) = rhs.log2_bounds();
293        if lhs_lo > rhs_hi {
294            return sign * Ordering::Greater;
295        }
296        if lhs_hi < rhs_lo {
297            return sign * Ordering::Less;
298        }
299
300        let rhs_exp = rhs.exponent();
301
302        // case 4: compare the exact values
303        let (mut lhs, mut rhs) = (lhs.numerator.clone(), rhs.significand() * &lhs.denominator);
304        if rhs_exp < 0 {
305            let exp = -rhs_exp as usize;
306            if B.is_power_of_two() {
307                lhs <<= exp * B.trailing_zeros() as usize;
308            } else {
309                lhs *= UBig::from_word(B).pow(exp);
310            }
311        } else {
312            let exp = rhs_exp as usize;
313            if B.is_power_of_two() {
314                rhs <<= exp * B.trailing_zeros() as usize;
315            } else {
316                rhs *= UBig::from_word(B).pow(exp);
317            }
318        }
319
320        if ABS {
321            lhs.abs_cmp(&rhs)
322        } else {
323            lhs.cmp(&rhs)
324        }
325    }
326
327    impl<R: Round, const B: Word> AbsOrd<FBig<R, B>> for RBig {
328        #[inline]
329        fn abs_cmp(&self, other: &FBig<R, B>) -> Ordering {
330            repr_cmp_fbig::<B, true>(&self.0, other.repr())
331        }
332    }
333
334    impl<R: Round, const B: Word> AbsOrd<FBig<R, B>> for Relaxed {
335        #[inline]
336        fn abs_cmp(&self, other: &FBig<R, B>) -> Ordering {
337            repr_cmp_fbig::<B, true>(&self.0, other.repr())
338        }
339    }
340
341    impl<R: Round, const B: Word> AbsOrd<RBig> for FBig<R, B> {
342        #[inline]
343        fn abs_cmp(&self, other: &RBig) -> Ordering {
344            repr_cmp_fbig::<B, true>(&other.0, self.repr()).reverse()
345        }
346    }
347
348    impl<R: Round, const B: Word> AbsOrd<Relaxed> for FBig<R, B> {
349        #[inline]
350        fn abs_cmp(&self, other: &Relaxed) -> Ordering {
351            repr_cmp_fbig::<B, true>(&other.0, self.repr()).reverse()
352        }
353    }
354}