Skip to main content

dashu_cmplx/
cmp.rs

1//! Comparison traits for [`CBig`].
2//!
3//! [`CBig`] mirrors [`dashu_float::FBig`]'s comparison surface rather than MPC's "complex has no
4//! order" stance: a lexicographic total [`Ord`] (by real part, then imaginary), an [`AbsOrd`]
5//! magnitude comparison via `|z|²`, and (behind `num-order`) `NumOrd`/`NumHash`.
6
7use crate::cbig::CBig;
8use crate::repr::Context;
9use core::cmp::Ordering;
10use dashu_base::AbsOrd;
11use dashu_float::round::Round;
12use dashu_float::Repr;
13use dashu_int::Word;
14
15/// Lexicographic comparison by `(re, then im)` using the value-based [`Repr`] order. This is a
16/// well-defined total order (usable for `BTreeMap`/sorting), not an algebraic one.
17pub(crate) fn lex_cmp<const B: Word>(
18    re1: &Repr<B>,
19    im1: &Repr<B>,
20    re2: &Repr<B>,
21    im2: &Repr<B>,
22) -> Ordering {
23    match re1.cmp(re2) {
24        Ordering::Equal => im1.cmp(im2),
25        ord => ord,
26    }
27}
28
29impl<R1: Round, R2: Round, const B: Word> PartialEq<CBig<R2, B>> for CBig<R1, B> {
30    /// Componentwise exact equality. `+0 == -0` per component (matching `FBig`); the context is
31    /// ignored.
32    #[inline]
33    fn eq(&self, other: &CBig<R2, B>) -> bool {
34        self.re == other.re && self.im == other.im
35    }
36}
37impl<R: Round, const B: Word> Eq for CBig<R, B> {}
38
39impl<R1: Round, R2: Round, const B: Word> PartialOrd<CBig<R2, B>> for CBig<R1, B> {
40    #[inline]
41    fn partial_cmp(&self, other: &CBig<R2, B>) -> Option<Ordering> {
42        Some(lex_cmp(&self.re, &self.im, &other.re, &other.im))
43    }
44}
45
46impl<R: Round, const B: Word> Ord for CBig<R, B> {
47    /// Lexicographic total order by `(re, then im)`. Special values are placed consistently with
48    /// `FBig` (`-∞ < finite < +∞` per component).
49    #[inline]
50    fn cmp(&self, other: &Self) -> Ordering {
51        lex_cmp(&self.re, &self.im, &other.re, &other.im)
52    }
53}
54
55impl<R: Round, const B: Word> AbsOrd for CBig<R, B> {
56    /// Magnitude comparison by `|z|`. Compared through `|z|²` (the exact squared modulus — both
57    /// sides are non-negative, so the order is preserved) to avoid the `sqrt`/`hypot` of [`CBig::abs`].
58    #[inline]
59    fn abs_cmp(&self, other: &Self) -> Ordering {
60        // Exact squared magnitudes at unlimited precision (no rounding, no overflow).
61        let unlim = Context::<R>::new(0);
62        let f = unlim.float();
63        let n1 = f.unwrap_fp(unlim.norm(self));
64        let n2 = f.unwrap_fp(unlim.norm(other));
65        n1.cmp(&n2)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use dashu_float::round::mode;
73
74    type C = CBig<mode::HalfAway, 10>;
75
76    #[test]
77    fn eq_componentwise() {
78        let a = C::from_parts(3.into(), 4.into());
79        let b = C::from_parts(3.into(), 4.into());
80        assert!(a == b);
81        let c = C::from_parts(3.into(), 5.into());
82        assert!(a != c);
83    }
84
85    #[test]
86    fn signed_zero_eq() {
87        let p = C::from_parts(3.into(), 0.into());
88        let n = C::new(Repr::new(3.into(), 0), Repr::neg_zero(), Context::new(0));
89        // +0 == -0 on the imaginary part
90        assert!(p == n);
91    }
92
93    #[test]
94    fn ord_lexicographic() {
95        let a = C::from_parts(1.into(), 9.into());
96        let b = C::from_parts(2.into(), 0.into());
97        assert!(a < b); // real part dominates
98        let c = C::from_parts(1.into(), 10.into());
99        assert!(a < c); // equal real, larger imag
100    }
101
102    #[test]
103    fn absord_by_magnitude() {
104        let a = C::from_parts(3.into(), 4.into()); // |z| = 5
105        let b = C::from_parts(5.into(), 0.into()); // |z| = 5
106        assert!(a.abs_cmp(&b).is_eq());
107        let c = C::from_parts(1.into(), 1.into()); // |z|² = 2
108        assert!(c.abs_cmp(&a).is_lt());
109    }
110}