Skip to main content

dashu_ratio/
cmp.rs

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