Skip to main content

dashu_float/
cmp.rs

1use core::cmp::Ordering;
2
3use dashu_base::{AbsOrd, EstimatedLog2, Sign};
4use dashu_int::{IBig, UBig};
5
6use crate::{
7    fbig::FBig,
8    repr::Repr,
9    repr::Word,
10    round::Round,
11    utils::{shl_digits, shl_digits_in_place},
12};
13
14impl<R1: Round, R2: Round, const B: Word> PartialEq<FBig<R2, B>> for FBig<R1, B> {
15    #[inline]
16    fn eq(&self, other: &FBig<R2, B>) -> bool {
17        match (self.repr.is_infinite(), other.repr.is_infinite()) {
18            // +inf == +inf, -inf == -inf
19            (true, true) => !((self.repr.exponent >= 0) ^ (other.repr.exponent >= 0)),
20
21            // the representation is normalized so direct comparing is okay,
22            // and the context doesn't count in comparison
23            (false, false) => self.repr == other.repr,
24
25            // inf != any exact numbers
26            (_, _) => false,
27        }
28    }
29}
30impl<R: Round, const B: Word> Eq for FBig<R, B> {}
31
32fn repr_cmp_same_base<const B: Word, const ABS: bool>(
33    lhs: &Repr<B>,
34    rhs: &Repr<B>,
35    precision: Option<(usize, usize)>,
36) -> Ordering {
37    // case 1: compare with inf
38    match (lhs.is_infinite(), rhs.is_infinite()) {
39        (true, true) => {
40            return if ABS {
41                Ordering::Equal
42            } else {
43                lhs.exponent.cmp(&rhs.exponent)
44            }
45        }
46        (false, true) => {
47            return match ABS || rhs.exponent >= 0 {
48                true => Ordering::Less,
49                false => Ordering::Greater,
50            }
51        }
52        (true, false) => {
53            return match ABS || lhs.exponent >= 0 {
54                true => Ordering::Greater,
55                false => Ordering::Less,
56            }
57        }
58        _ => {}
59    };
60
61    // case 2: compare sign
62    let sign = if ABS {
63        Sign::Positive
64    } else {
65        match (lhs.significand.sign(), rhs.significand.sign()) {
66            (Sign::Positive, Sign::Positive) => Sign::Positive,
67            (Sign::Positive, Sign::Negative) => return Ordering::Greater,
68            (Sign::Negative, Sign::Positive) => return Ordering::Less,
69            (Sign::Negative, Sign::Negative) => Sign::Negative,
70        }
71    };
72
73    // case 3: compare with 0 (both +0 and -0 are zero)
74    match (lhs.significand.is_zero(), rhs.significand.is_zero()) {
75        (true, true) => return Ordering::Equal,
76        (true, false) => {
77            // rhs must be positive, otherwise case 2 will return
78            return Ordering::Less;
79        }
80        (false, true) => {
81            // lhs must be positive, otherwise case 2 will return
82            return Ordering::Greater;
83        }
84        _ => {}
85    }
86
87    // case 4: compare exponent and precision
88    let (lhs_exp, rhs_exp) = (lhs.exponent, rhs.exponent);
89    if let Some((lhs_prec, rhs_prec)) = precision {
90        // only compare when both number are not having arbitrary precision
91        if lhs_prec != 0 && rhs_prec != 0 {
92            // Saturating: an exponent near `isize::MAX` (e.g. the result of `powi(2, n)` for a
93            // near-max `n`, which is representable so the range guard doesn't short-circuit it)
94            // makes `exp + precision` overflow. These are magnitude shortcuts only — saturating
95            // just forgoes the shortcut (falling through to the exact case 6), it never mis-orders.
96            if lhs_exp > rhs_exp.saturating_add(rhs_prec as isize) {
97                return sign * Ordering::Greater;
98            }
99            if rhs_exp > lhs_exp.saturating_add(lhs_prec as isize) {
100                return sign * Ordering::Less;
101            }
102        }
103    }
104
105    // case 5: compare exponent and digits
106    let (lhs_digits, rhs_digits) = (lhs.digits_ub(), rhs.digits_ub());
107    if lhs_exp > rhs_exp.saturating_add(rhs_digits as isize) {
108        return sign * Ordering::Greater;
109    }
110    if rhs_exp > lhs_exp.saturating_add(lhs_digits as isize) {
111        return sign * Ordering::Less;
112    }
113
114    // case 6: compare exact values by shifting
115    let (lhs_signif, rhs_signif) = (&lhs.significand, &rhs.significand);
116    if ABS {
117        match lhs_exp.cmp(&rhs_exp) {
118            Ordering::Equal => lhs_signif.abs_cmp(rhs_signif),
119            Ordering::Greater => {
120                shl_digits::<B>(lhs_signif, (lhs_exp - rhs_exp) as usize).abs_cmp(rhs_signif)
121            }
122            Ordering::Less => {
123                lhs_signif.abs_cmp(&shl_digits::<B>(rhs_signif, (rhs_exp - lhs_exp) as usize))
124            }
125        }
126    } else {
127        match lhs_exp.cmp(&rhs_exp) {
128            Ordering::Equal => lhs_signif.cmp(rhs_signif),
129            Ordering::Greater => {
130                shl_digits::<B>(lhs_signif, (lhs_exp - rhs_exp) as usize).cmp(rhs_signif)
131            }
132            Ordering::Less => {
133                lhs_signif.cmp(&shl_digits::<B>(rhs_signif, (rhs_exp - lhs_exp) as usize))
134            }
135        }
136    }
137}
138
139impl<const B: Word> PartialOrd for Repr<B> {
140    #[inline]
141    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
142        Some(self.cmp(other))
143    }
144}
145
146impl<const B: Word> Ord for Repr<B> {
147    #[inline]
148    fn cmp(&self, other: &Self) -> Ordering {
149        repr_cmp_same_base::<B, false>(self, other, None)
150    }
151}
152
153impl<R1: Round, R2: Round, const B: Word> PartialOrd<FBig<R2, B>> for FBig<R1, B> {
154    #[inline]
155    fn partial_cmp(&self, other: &FBig<R2, B>) -> Option<Ordering> {
156        Some(repr_cmp_same_base::<B, false>(
157            &self.repr,
158            &other.repr,
159            Some((self.context.precision, other.context.precision)),
160        ))
161    }
162}
163
164impl<R: Round, const B: Word> Ord for FBig<R, B> {
165    #[inline]
166    fn cmp(&self, other: &Self) -> Ordering {
167        repr_cmp_same_base::<B, false>(
168            &self.repr,
169            &other.repr,
170            Some((self.context.precision, other.context.precision)),
171        )
172    }
173}
174
175impl<R: Round, const B: Word> AbsOrd for FBig<R, B> {
176    #[inline]
177    fn abs_cmp(&self, other: &Self) -> Ordering {
178        repr_cmp_same_base::<B, true>(
179            &self.repr,
180            &other.repr,
181            Some((self.context.precision, other.context.precision)),
182        )
183    }
184}
185
186pub(crate) fn repr_cmp_ubig<const B: Word, const ABS: bool>(lhs: &Repr<B>, rhs: &UBig) -> Ordering {
187    // case 1: compare with inf
188    if lhs.is_infinite() {
189        return if lhs.exponent > 0 || ABS {
190            Ordering::Greater
191        } else {
192            Ordering::Less
193        };
194    }
195
196    // case 2: compare sign
197    if !ABS && lhs.significand.sign() == Sign::Negative {
198        return Ordering::Less;
199    }
200
201    // case 3: compare log2 estimations
202    let (lhs_lo, lhs_hi) = lhs.log2_bounds();
203    let (rhs_lo, rhs_hi) = rhs.log2_bounds();
204    if lhs_lo > rhs_hi {
205        return Ordering::Greater;
206    }
207    if lhs_hi < rhs_lo {
208        return Ordering::Less;
209    }
210
211    // case 4: compare the exact values
212    let mut rhs: IBig = rhs.clone().into();
213    if lhs.exponent < 0 {
214        shl_digits_in_place::<B>(&mut rhs, (-lhs.exponent) as usize);
215        lhs.significand.cmp(&rhs)
216    } else {
217        shl_digits::<B>(&lhs.significand, lhs.exponent as usize).cmp(&rhs)
218    }
219}
220
221pub(crate) fn repr_cmp_ibig<const B: Word, const ABS: bool>(lhs: &Repr<B>, rhs: &IBig) -> Ordering {
222    // case 1: compare with inf
223    if lhs.is_infinite() {
224        return if lhs.exponent > 0 || ABS {
225            Ordering::Greater
226        } else {
227            Ordering::Less
228        };
229    }
230
231    // case 2: compare sign
232    let sign = if ABS {
233        Sign::Positive
234    } else {
235        match (lhs.significand.sign(), rhs.sign()) {
236            (Sign::Positive, Sign::Positive) => Sign::Positive,
237            (Sign::Positive, Sign::Negative) => return Ordering::Greater,
238            (Sign::Negative, Sign::Positive) => return Ordering::Less,
239            (Sign::Negative, Sign::Negative) => Sign::Negative,
240        }
241    };
242
243    // case 3: compare log2 estimations
244    let (lhs_lo, lhs_hi) = lhs.log2_bounds();
245    let (rhs_lo, rhs_hi) = rhs.log2_bounds();
246    if lhs_lo > rhs_hi {
247        return sign * Ordering::Greater;
248    }
249    if lhs_hi < rhs_lo {
250        return sign * Ordering::Less;
251    }
252
253    // case 4: compare the exact values
254    if lhs.exponent < 0 {
255        lhs.significand
256            .cmp(&shl_digits::<B>(rhs, (-lhs.exponent) as usize))
257    } else {
258        shl_digits::<B>(&lhs.significand, lhs.exponent as usize).cmp(rhs)
259    }
260}
261
262macro_rules! impl_abs_ord_with_method {
263    ($T:ty, $method:ident) => {
264        impl<const B: Word> AbsOrd<$T> for Repr<B> {
265            #[inline]
266            fn abs_cmp(&self, other: &$T) -> Ordering {
267                $method::<B, true>(self, other)
268            }
269        }
270        impl<const B: Word> AbsOrd<Repr<B>> for $T {
271            #[inline]
272            fn abs_cmp(&self, other: &Repr<B>) -> Ordering {
273                $method::<B, true>(other, self).reverse()
274            }
275        }
276        impl<R: Round, const B: Word> AbsOrd<$T> for FBig<R, B> {
277            #[inline]
278            fn abs_cmp(&self, other: &$T) -> Ordering {
279                $method::<B, true>(&self.repr, other)
280            }
281        }
282        impl<R: Round, const B: Word> AbsOrd<FBig<R, B>> for $T {
283            #[inline]
284            fn abs_cmp(&self, other: &FBig<R, B>) -> Ordering {
285                $method::<B, true>(&other.repr, self).reverse()
286            }
287        }
288    };
289}
290impl_abs_ord_with_method!(UBig, repr_cmp_ubig);
291impl_abs_ord_with_method!(IBig, repr_cmp_ibig);