Skip to main content

logicaffeine_base/
numeric.rs

1//! The numeric tower's foundation: a hand-rolled arbitrary-precision integer.
2//!
3//! Logos carries the *type* of a number across every boundary (interpreter, VM,
4//! wire), so an integer never silently becomes an IEEE-754 double the way a JSON
5//! number does — there is no 2^53 cliff here. [`BigInt`] is the exact, unbounded
6//! integer all of that rests on; [`Rational`]/[`Decimal`]/[`Complex`] build on it.
7//!
8//! Representation is sign + little-endian base-2^64 magnitude (limbs, no
9//! trailing zeros; zero is the empty magnitude). A single-limb magnitude is
10//! stored inline — no heap allocation for anything that fits 64 bits, and the
11//! add/mul/div_rem fast paths compute those cases in machine registers.
12//! Arithmetic here is *correct first* — schoolbook add/sub/mul and
13//! bit-at-a-time long division — which is the exact-determinism floor;
14//! Karatsuba multiplication and Knuth-D division are the FAST follow-up that
15//! must reproduce these results bit-for-bit.
16
17use std::cmp::Ordering;
18use std::fmt;
19
20/// The sign of a [`BigInt`]. Zero has its own sign so the magnitude invariant
21/// (no trailing zero limbs; the zero magnitude is empty) stays canonical.
22#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
23enum Sign {
24    Neg,
25    Zero,
26    Pos,
27}
28
29/// An exact, arbitrary-precision integer.
30#[derive(Clone, PartialEq, Eq, Hash)]
31pub struct BigInt {
32    sign: Sign,
33    mag: Mag,
34}
35
36/// The magnitude of a [`BigInt`]: a single inline limb (the overwhelmingly
37/// common case — no heap allocation) or heap limbs for multi-limb values.
38///
39/// INVARIANT: `Heap` holds ≥ 2 normalized limbs (no trailing zeros); every
40/// 0- or 1-limb magnitude is `Inline` (zero is `Inline(0)`). The variant is
41/// therefore canonical per value, so the derived `PartialEq`/`Hash` agree
42/// with numeric equality.
43#[derive(Clone, PartialEq, Eq, Hash)]
44enum Mag {
45    Inline(u64),
46    Heap(Vec<u64>),
47}
48
49impl Mag {
50    /// Canonicalize a limb vector (trim trailing zeros, inline when it fits).
51    fn from_vec(mut v: Vec<u64>) -> Mag {
52        while v.last() == Some(&0) {
53            v.pop();
54        }
55        match v.len() {
56            0 => Mag::Inline(0),
57            1 => Mag::Inline(v[0]),
58            _ => Mag::Heap(v),
59        }
60    }
61
62    /// The normalized little-endian limbs (empty ⇔ zero).
63    fn limbs(&self) -> &[u64] {
64        match self {
65            Mag::Inline(0) => &[],
66            Mag::Inline(x) => std::slice::from_ref(x),
67            Mag::Heap(v) => v,
68        }
69    }
70
71    /// The single limb when the magnitude fits one (`Some(0)` for zero),
72    /// `None` for multi-limb values.
73    fn as_single(&self) -> Option<u64> {
74        match self {
75            Mag::Inline(x) => Some(*x),
76            Mag::Heap(_) => None,
77        }
78    }
79}
80
81// ---- magnitude primitives (operate on normalized little-endian `&[u64]`) ----
82
83/// Drop trailing zero limbs so a magnitude has a unique representation.
84fn normalize(mut mag: Vec<u64>) -> Vec<u64> {
85    while mag.last() == Some(&0) {
86        mag.pop();
87    }
88    mag
89}
90
91/// Compare two normalized magnitudes.
92fn mag_cmp(a: &[u64], b: &[u64]) -> Ordering {
93    match a.len().cmp(&b.len()) {
94        Ordering::Equal => {
95            for i in (0..a.len()).rev() {
96                match a[i].cmp(&b[i]) {
97                    Ordering::Equal => {}
98                    other => return other,
99                }
100            }
101            Ordering::Equal
102        }
103        other => other,
104    }
105}
106
107/// `a + b` on magnitudes (full carry).
108fn mag_add(a: &[u64], b: &[u64]) -> Vec<u64> {
109    let mut out = Vec::with_capacity(a.len().max(b.len()) + 1);
110    let mut carry = 0u128;
111    for i in 0..a.len().max(b.len()) {
112        let av = *a.get(i).unwrap_or(&0) as u128;
113        let bv = *b.get(i).unwrap_or(&0) as u128;
114        let sum = av + bv + carry;
115        out.push(sum as u64);
116        carry = sum >> 64;
117    }
118    if carry != 0 {
119        out.push(carry as u64);
120    }
121    normalize(out)
122}
123
124/// `a - b` on magnitudes; requires `a >= b` (full borrow).
125fn mag_sub(a: &[u64], b: &[u64]) -> Vec<u64> {
126    debug_assert!(mag_cmp(a, b) != Ordering::Less, "mag_sub underflow");
127    let mut out = Vec::with_capacity(a.len());
128    let mut borrow = 0i128;
129    for i in 0..a.len() {
130        let av = a[i] as i128;
131        let bv = *b.get(i).unwrap_or(&0) as i128;
132        let mut diff = av - bv - borrow;
133        if diff < 0 {
134            diff += 1i128 << 64;
135            borrow = 1;
136        } else {
137            borrow = 0;
138        }
139        out.push(diff as u64);
140    }
141    debug_assert_eq!(borrow, 0, "mag_sub left a borrow");
142    normalize(out)
143}
144
145/// Schoolbook `a * b` on magnitudes (Karatsuba is the FAST follow-up).
146fn mag_mul(a: &[u64], b: &[u64]) -> Vec<u64> {
147    if a.is_empty() || b.is_empty() {
148        return Vec::new();
149    }
150    let mut out = vec![0u64; a.len() + b.len()];
151    for (i, &av) in a.iter().enumerate() {
152        let mut carry = 0u128;
153        for (j, &bv) in b.iter().enumerate() {
154            let cur = out[i + j] as u128 + (av as u128) * (bv as u128) + carry;
155            out[i + j] = cur as u64;
156            carry = cur >> 64;
157        }
158        out[i + b.len()] += carry as u64;
159    }
160    normalize(out)
161}
162
163/// `r << 1 | bit` on a magnitude (shift the whole number up by one bit).
164fn mag_shl1(a: &[u64], bit: u64) -> Vec<u64> {
165    let mut out = Vec::with_capacity(a.len() + 1);
166    let mut carry = bit & 1;
167    for &limb in a {
168        out.push((limb << 1) | carry);
169        carry = limb >> 63;
170    }
171    if carry != 0 {
172        out.push(carry);
173    }
174    normalize(out)
175}
176
177/// Long division on magnitudes: returns `(quotient, remainder)` with
178/// `a = q*b + r` and `0 <= r < b`. Bit-at-a-time — correct and simple (the
179/// exact-determinism oracle); Knuth Algorithm D is the FAST replacement.
180fn mag_divrem(a: &[u64], b: &[u64]) -> (Vec<u64>, Vec<u64>) {
181    debug_assert!(!b.is_empty(), "division by zero magnitude");
182    if mag_cmp(a, b) == Ordering::Less {
183        return (Vec::new(), a.to_vec());
184    }
185    let nbits = a.len() * 64;
186    let mut q = vec![0u64; a.len()];
187    let mut r: Vec<u64> = Vec::new();
188    for i in (0..nbits).rev() {
189        let bit = (a[i / 64] >> (i % 64)) & 1;
190        r = mag_shl1(&r, bit);
191        if mag_cmp(&r, b) != Ordering::Less {
192            r = mag_sub(&r, b);
193            q[i / 64] |= 1u64 << (i % 64);
194        }
195    }
196    (normalize(q), normalize(r))
197}
198
199impl BigInt {
200    /// The additive identity.
201    pub fn zero() -> Self {
202        BigInt { sign: Sign::Zero, mag: Mag::Inline(0) }
203    }
204
205    /// Build from a sign flag and a (not-necessarily-normalized) magnitude,
206    /// re-establishing the canonical form (trim zeros; empty magnitude ⇒ Zero).
207    fn from_sign_mag(neg: bool, mag: Vec<u64>) -> Self {
208        let mag = Mag::from_vec(mag);
209        let sign = if mag.limbs().is_empty() {
210            Sign::Zero
211        } else if neg {
212            Sign::Neg
213        } else {
214            Sign::Pos
215        };
216        BigInt { sign, mag }
217    }
218
219    /// Build from a sign flag and a ≤128-bit magnitude — the allocation-free
220    /// constructor the single-limb fast paths return through.
221    fn from_sign_mag_u128(neg: bool, m: u128) -> Self {
222        if m == 0 {
223            return Self::zero();
224        }
225        let lo = m as u64;
226        let hi = (m >> 64) as u64;
227        let mag = if hi == 0 { Mag::Inline(lo) } else { Mag::Heap(vec![lo, hi]) };
228        BigInt { sign: if neg { Sign::Neg } else { Sign::Pos }, mag }
229    }
230
231    /// Exact widening from a machine integer.
232    pub fn from_i64(x: i64) -> Self {
233        Self::from_sign_mag_u128(x < 0, (x as i128).unsigned_abs())
234    }
235
236    /// Exact widening from an unsigned machine integer.
237    pub fn from_u64(x: u64) -> Self {
238        Self::from_sign_mag_u128(false, x as u128)
239    }
240
241    /// Narrow back to an `i64` iff the value fits — the basis of the "downsize when
242    /// it provably fits" path the runtime uses to stay on the fast i64 repr.
243    pub fn to_i64(&self) -> Option<i64> {
244        let m = self.mag.as_single()? as i128;
245        let v = if self.sign == Sign::Neg { -m } else { m };
246        if (i64::MIN as i128..=i64::MAX as i128).contains(&v) {
247            Some(v as i64)
248        } else {
249            None
250        }
251    }
252
253    pub fn is_zero(&self) -> bool {
254        self.sign == Sign::Zero
255    }
256
257    /// Parity (the low bit of the magnitude — sign does not change it). Zero is even.
258    pub fn is_odd(&self) -> bool {
259        self.mag.limbs().first().is_some_and(|limb| limb & 1 == 1)
260    }
261
262    /// Nearest `f64` (lossy by nature — float semantics) for mixed int/float math.
263    pub fn to_f64(&self) -> f64 {
264        const TWO64: f64 = 18_446_744_073_709_551_616.0; // 2^64
265        let mut acc = 0.0f64;
266        for &limb in self.mag.limbs().iter().rev() {
267            acc = acc * TWO64 + limb as f64;
268        }
269        if self.sign == Sign::Neg {
270            -acc
271        } else {
272            acc
273        }
274    }
275
276    pub fn is_negative(&self) -> bool {
277        self.sign == Sign::Neg
278    }
279
280    pub fn is_positive(&self) -> bool {
281        self.sign == Sign::Pos
282    }
283
284    /// The absolute value.
285    pub fn abs(&self) -> Self {
286        BigInt { sign: if self.sign == Sign::Zero { Sign::Zero } else { Sign::Pos }, mag: self.mag.clone() }
287    }
288
289    /// Additive inverse.
290    pub fn negated(&self) -> Self {
291        let sign = match self.sign {
292            Sign::Neg => Sign::Pos,
293            Sign::Zero => Sign::Zero,
294            Sign::Pos => Sign::Neg,
295        };
296        BigInt { sign, mag: self.mag.clone() }
297    }
298
299    /// `self + other`.
300    pub fn add(&self, other: &Self) -> Self {
301        // Single-limb fast path: both magnitudes fit a limb, so the signed sum
302        // fits an i128 — no limb vectors, no allocation.
303        if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
304            let av = if self.sign == Sign::Neg { -(a as i128) } else { a as i128 };
305            let bv = if other.sign == Sign::Neg { -(b as i128) } else { b as i128 };
306            let sum = av + bv;
307            return Self::from_sign_mag_u128(sum < 0, sum.unsigned_abs());
308        }
309        match (self.sign, other.sign) {
310            (Sign::Zero, _) => other.clone(),
311            (_, Sign::Zero) => self.clone(),
312            // Same sign: add magnitudes, keep the sign.
313            (a, b) if a == b => {
314                Self::from_sign_mag(a == Sign::Neg, mag_add(self.mag.limbs(), other.mag.limbs()))
315            }
316            // Opposite signs: subtract the smaller magnitude from the larger; the
317            // result takes the sign of the larger.
318            _ => match mag_cmp(self.mag.limbs(), other.mag.limbs()) {
319                Ordering::Equal => Self::zero(),
320                Ordering::Greater => Self::from_sign_mag(
321                    self.sign == Sign::Neg,
322                    mag_sub(self.mag.limbs(), other.mag.limbs()),
323                ),
324                Ordering::Less => Self::from_sign_mag(
325                    other.sign == Sign::Neg,
326                    mag_sub(other.mag.limbs(), self.mag.limbs()),
327                ),
328            },
329        }
330    }
331
332    /// `self - other`.
333    pub fn sub(&self, other: &Self) -> Self {
334        self.add(&other.negated())
335    }
336
337    /// `self * other`.
338    pub fn mul(&self, other: &Self) -> Self {
339        let neg = (self.sign == Sign::Neg) ^ (other.sign == Sign::Neg);
340        // Single-limb fast path: the product fits a u128.
341        if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
342            return Self::from_sign_mag_u128(neg, (a as u128) * (b as u128));
343        }
344        if self.is_zero() || other.is_zero() {
345            return Self::zero();
346        }
347        Self::from_sign_mag(neg, mag_mul(self.mag.limbs(), other.mag.limbs()))
348    }
349
350    /// Truncated division toward zero: returns `(quotient, remainder)` with
351    /// `self = q*other + r` and the remainder carrying the dividend's sign — exactly
352    /// matching Rust/`i64` `/` and `%`, so the wide type is a drop-in for the narrow.
353    /// `None` when `other` is zero.
354    pub fn div_rem(&self, other: &Self) -> Option<(Self, Self)> {
355        if other.is_zero() {
356            return None;
357        }
358        let q_neg = (self.sign == Sign::Neg) ^ (other.sign == Sign::Neg);
359        // Single-limb fast path: machine division gives the truncated pair
360        // directly (the remainder takes the dividend's sign).
361        if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
362            return Some((
363                Self::from_sign_mag_u128(q_neg, (a / b) as u128),
364                Self::from_sign_mag_u128(self.sign == Sign::Neg, (a % b) as u128),
365            ));
366        }
367        if self.is_zero() {
368            return Some((Self::zero(), Self::zero()));
369        }
370        let (qm, rm) = mag_divrem(self.mag.limbs(), other.mag.limbs());
371        let q = Self::from_sign_mag(q_neg, qm);
372        // The remainder takes the dividend's sign (truncated division).
373        let r = Self::from_sign_mag(self.sign == Sign::Neg, rm);
374        Some((q, r))
375    }
376
377    /// Parse a base-10 integer (optional leading `+`/`-`). `None` on any non-digit.
378    pub fn parse_decimal(s: &str) -> Option<Self> {
379        let bytes = s.as_bytes();
380        let (neg, digits) = match bytes.first() {
381            Some(b'-') => (true, &bytes[1..]),
382            Some(b'+') => (false, &bytes[1..]),
383            _ => (false, bytes),
384        };
385        if digits.is_empty() {
386            return None;
387        }
388        let ten = BigInt::from_u64(10);
389        let mut acc = BigInt::zero();
390        for &d in digits {
391            if !d.is_ascii_digit() {
392                return None;
393            }
394            acc = acc.mul(&ten).add(&BigInt::from_u64((d - b'0') as u64));
395        }
396        Some(if neg { acc.negated() } else { acc })
397    }
398
399    /// `self` raised to a non-negative integer power, by exponentiation-by-squaring
400    /// (`0^0 == 1`). Exact and unbounded — `2.pow(63)` is the value `i64` cannot hold.
401    pub fn pow(&self, mut exp: u32) -> BigInt {
402        let mut result = BigInt::from_u64(1);
403        let mut base = self.clone();
404        while exp > 0 {
405            if exp & 1 == 1 {
406                result = result.mul(&base);
407            }
408            exp >>= 1;
409            if exp > 0 {
410                base = base.mul(&base);
411            }
412        }
413        result
414    }
415
416    /// Sign + little-endian magnitude bytes — a compact, exact serialization (the
417    /// inverse of [`BigInt::from_le_bytes`]). The wire ships these instead of a
418    /// decimal string, so there is no base conversion and no precision question.
419    pub fn to_le_bytes(&self) -> (bool, Vec<u8>) {
420        let limbs = self.mag.limbs();
421        let mut bytes = Vec::with_capacity(limbs.len() * 8);
422        for &limb in limbs {
423            bytes.extend_from_slice(&limb.to_le_bytes());
424        }
425        (self.sign == Sign::Neg, bytes)
426    }
427
428    /// Reconstruct from a sign flag and little-endian magnitude bytes (length need
429    /// not be a multiple of 8; trailing zero limbs are normalized away).
430    pub fn from_le_bytes(negative: bool, bytes: &[u8]) -> Self {
431        let mut mag = Vec::with_capacity(bytes.len().div_ceil(8));
432        for chunk in bytes.chunks(8) {
433            let mut limb = [0u8; 8];
434            limb[..chunk.len()].copy_from_slice(chunk);
435            mag.push(u64::from_le_bytes(limb));
436        }
437        Self::from_sign_mag(negative, mag)
438    }
439}
440
441impl Ord for BigInt {
442    fn cmp(&self, other: &Self) -> Ordering {
443        // Order by sign first, then by magnitude (reversed when both negative).
444        let rank = |s: Sign| match s {
445            Sign::Neg => -1i8,
446            Sign::Zero => 0,
447            Sign::Pos => 1,
448        };
449        match rank(self.sign).cmp(&rank(other.sign)) {
450            Ordering::Equal => match self.sign {
451                Sign::Zero => Ordering::Equal,
452                Sign::Pos => mag_cmp(self.mag.limbs(), other.mag.limbs()),
453                Sign::Neg => mag_cmp(other.mag.limbs(), self.mag.limbs()),
454            },
455            other => other,
456        }
457    }
458}
459
460impl PartialOrd for BigInt {
461    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
462        Some(self.cmp(other))
463    }
464}
465
466impl fmt::Display for BigInt {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        if self.is_zero() {
469            return write!(f, "0");
470        }
471        // Emit base-10 by repeatedly dividing by 10^19 (the largest power of ten that
472        // fits in a u64), collecting 19-digit chunks least-significant first.
473        const TEN19: u64 = 10_000_000_000_000_000_000;
474        let ten19 = BigInt::from_u64(TEN19);
475        let mut chunks: Vec<u64> = Vec::new();
476        let mut cur = self.abs();
477        while !cur.is_zero() {
478            let (q, r) = cur.div_rem(&ten19).expect("10^19 is nonzero");
479            // r < 10^19 < 2^64, so it is one limb at most — and may exceed i64::MAX,
480            // hence read the limb directly rather than via `to_i64`.
481            chunks.push(r.mag.limbs().first().copied().unwrap_or(0));
482            cur = q;
483        }
484        if self.is_negative() {
485            write!(f, "-")?;
486        }
487        // Most-significant chunk has no leading zeros; the rest are zero-padded to 19.
488        write!(f, "{}", chunks.last().unwrap())?;
489        for &chunk in chunks.iter().rev().skip(1) {
490            write!(f, "{chunk:019}")?;
491        }
492        Ok(())
493    }
494}
495
496impl fmt::Debug for BigInt {
497    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498        write!(f, "BigInt({self})")
499    }
500}
501
502impl From<i64> for BigInt {
503    fn from(x: i64) -> Self {
504        BigInt::from_i64(x)
505    }
506}
507
508// =====================================================================
509// Rational — exact fractions on top of BigInt
510// =====================================================================
511
512/// `gcd(|a|, |b|)` by the Euclidean algorithm (`gcd(a, 0) == |a|`).
513fn bigint_gcd(a: &BigInt, b: &BigInt) -> BigInt {
514    let mut a = a.abs();
515    let mut b = b.abs();
516    while !b.is_zero() {
517        let (_q, r) = a.div_rem(&b).expect("b is nonzero inside the loop");
518        a = b;
519        b = r;
520    }
521    a
522}
523
524/// An exact rational number: a fraction kept in lowest terms with a strictly
525/// positive denominator. Built on [`BigInt`], so it never rounds the way a JSON
526/// / `f64` "number" does — `1/3` stays exactly `1/3`, not `0.3333…`, and a
527/// numerator past 2^53 survives instead of collapsing onto a double.
528///
529/// Representation is correct-first: BigInt numerator and denominator, reduced on
530/// every construction. The i64-fast-path (storing small num/den inline to skip
531/// the BigInt allocation) is the documented performance follow-up — exactly as
532/// Karatsuba is for [`BigInt`] — and must reproduce these values bit-for-bit.
533#[derive(Clone, PartialEq, Eq, Hash)]
534pub struct Rational {
535    /// Carries the sign of the whole value.
536    num: BigInt,
537    /// INVARIANT: `den > 0`, `gcd(|num|, den) == 1`, and `den == 1` whenever
538    /// `num == 0` — so equal values share one representation (Eq/Hash/Ord are
539    /// structural).
540    den: BigInt,
541}
542
543impl Rational {
544    /// The canonicalizing constructor: reduce `num/den` to lowest terms with a
545    /// positive denominator. Returns `None` for a zero denominator (the one
546    /// undefined fraction).
547    pub fn new(num: BigInt, den: BigInt) -> Option<Rational> {
548        if den.is_zero() {
549            return None;
550        }
551        let (mut num, mut den) = (num, den);
552        if den.is_negative() {
553            num = num.negated();
554            den = den.negated();
555        }
556        if num.is_zero() {
557            return Some(Rational { num: BigInt::zero(), den: BigInt::from_i64(1) });
558        }
559        let g = bigint_gcd(&num, &den);
560        let (num, _) = num.div_rem(&g).expect("gcd divides the numerator exactly");
561        let (den, _) = den.div_rem(&g).expect("gcd divides the denominator exactly");
562        Some(Rational { num, den })
563    }
564
565    /// The integer `n` as the fraction `n/1`.
566    pub fn from_bigint(n: BigInt) -> Rational {
567        Rational { num: n, den: BigInt::from_i64(1) }
568    }
569
570    pub fn from_i64(x: i64) -> Rational {
571        Rational::from_bigint(BigInt::from_i64(x))
572    }
573
574    /// `n/d` from machine integers (convenience; `None` if `d == 0`).
575    pub fn from_ratio_i64(n: i64, d: i64) -> Option<Rational> {
576        Rational::new(BigInt::from_i64(n), BigInt::from_i64(d))
577    }
578
579    pub fn zero() -> Rational {
580        Rational { num: BigInt::zero(), den: BigInt::from_i64(1) }
581    }
582
583    pub fn one() -> Rational {
584        Rational::from_i64(1)
585    }
586
587    pub fn numerator(&self) -> &BigInt {
588        &self.num
589    }
590
591    pub fn denominator(&self) -> &BigInt {
592        &self.den
593    }
594
595    /// True when the value is a whole number (`den == 1`).
596    pub fn is_integer(&self) -> bool {
597        self.den == BigInt::from_i64(1)
598    }
599
600    pub fn is_zero(&self) -> bool {
601        self.num.is_zero()
602    }
603
604    pub fn is_negative(&self) -> bool {
605        self.num.is_negative()
606    }
607
608    pub fn is_positive(&self) -> bool {
609        self.num.is_positive()
610    }
611
612    /// The integer value when this is whole, else `None` (the provable narrow
613    /// back to [`BigInt`]).
614    pub fn to_bigint(&self) -> Option<BigInt> {
615        if self.is_integer() {
616            Some(self.num.clone())
617        } else {
618            None
619        }
620    }
621
622    /// The `i64` value when this is a whole number that fits, else `None`.
623    pub fn to_i64(&self) -> Option<i64> {
624        if self.is_integer() {
625            self.num.to_i64()
626        } else {
627            None
628        }
629    }
630
631    /// Nearest `f64` (for display / interop only — lossy for large terms, exact
632    /// for small ones). The *value* stays exact; this is a view.
633    pub fn to_f64(&self) -> f64 {
634        self.num.to_f64() / self.den.to_f64()
635    }
636
637    /// The greatest integer `≤ self` (round toward −∞). `floor(7/2) == 3`,
638    /// `floor(-7/2) == -4`. The companion of explicit floor division.
639    pub fn floor(&self) -> BigInt {
640        let (q, r) = self.num.div_rem(&self.den).expect("denominator is nonzero");
641        if self.num.is_negative() && !r.is_zero() {
642            q.sub(&BigInt::from_i64(1))
643        } else {
644            q
645        }
646    }
647
648    /// The least integer `≥ self` (round toward +∞). `ceil(7/2) == 4`,
649    /// `ceil(-7/2) == -3`.
650    pub fn ceil(&self) -> BigInt {
651        let (q, r) = self.num.div_rem(&self.den).expect("denominator is nonzero");
652        if !self.num.is_negative() && !r.is_zero() {
653            q.add(&BigInt::from_i64(1))
654        } else {
655            q
656        }
657    }
658
659    /// The nearest integer, ties rounded AWAY from zero (matching `f64::round`):
660    /// `round(x) = sign(x) · ⌊|x| + 1/2⌋ = sign(x) · ((2|num| + den) ÷ 2den)`.
661    pub fn round(&self) -> BigInt {
662        let two = BigInt::from_i64(2);
663        let numerator = self.num.abs().mul(&two).add(&self.den);
664        let denominator = self.den.mul(&two);
665        let (mag, _) = numerator.div_rem(&denominator).expect("denominator is nonzero");
666        if self.num.is_negative() {
667            mag.negated()
668        } else {
669            mag
670        }
671    }
672
673    pub fn negated(&self) -> Rational {
674        Rational { num: self.num.negated(), den: self.den.clone() }
675    }
676
677    pub fn abs(&self) -> Rational {
678        Rational { num: self.num.abs(), den: self.den.clone() }
679    }
680
681    /// `1/self` — `None` when `self == 0`.
682    pub fn recip(&self) -> Option<Rational> {
683        Rational::new(self.den.clone(), self.num.clone())
684    }
685
686    pub fn add(&self, other: &Rational) -> Rational {
687        // a/b + c/d = (a·d + c·b)/(b·d); b,d > 0 ⇒ b·d > 0 ⇒ `new` succeeds.
688        let num = self.num.mul(&other.den).add(&other.num.mul(&self.den));
689        let den = self.den.mul(&other.den);
690        Rational::new(num, den).expect("product of positive denominators is nonzero")
691    }
692
693    pub fn sub(&self, other: &Rational) -> Rational {
694        let num = self.num.mul(&other.den).sub(&other.num.mul(&self.den));
695        let den = self.den.mul(&other.den);
696        Rational::new(num, den).expect("product of positive denominators is nonzero")
697    }
698
699    pub fn mul(&self, other: &Rational) -> Rational {
700        let num = self.num.mul(&other.num);
701        let den = self.den.mul(&other.den);
702        Rational::new(num, den).expect("product of positive denominators is nonzero")
703    }
704
705    /// `self / other` — `None` when `other == 0`.
706    pub fn div(&self, other: &Rational) -> Option<Rational> {
707        // (a/b)/(c/d) = (a·d)/(b·c); `new` rejects a zero denominator (c == 0).
708        let num = self.num.mul(&other.den);
709        let den = self.den.mul(&other.num);
710        Rational::new(num, den)
711    }
712
713    /// `self^exp`, exact for every integer exponent. Negative exponents take the
714    /// reciprocal first; `None` only for `0` raised to a negative power.
715    pub fn pow(&self, exp: i32) -> Option<Rational> {
716        if exp >= 0 {
717            let k = exp as u32;
718            Some(
719                Rational::new(self.num.pow(k), self.den.pow(k))
720                    .expect("denominator^k stays positive"),
721            )
722        } else {
723            if self.num.is_zero() {
724                return None;
725            }
726            let k = exp.unsigned_abs();
727            // (a/b)^-k = b^k / a^k; `new` re-fixes the sign and reduces.
728            Rational::new(self.den.pow(k), self.num.pow(k))
729        }
730    }
731
732    /// Parse `"3/4"`, `"-3/4"`, or a bare integer `"5"`. Whitespace around the
733    /// parts is tolerated; `None` on malformed input or a zero denominator.
734    pub fn parse(s: &str) -> Option<Rational> {
735        let s = s.trim();
736        if let Some((n, d)) = s.split_once('/') {
737            let num = BigInt::parse_decimal(n.trim())?;
738            let den = BigInt::parse_decimal(d.trim())?;
739            Rational::new(num, den)
740        } else {
741            Some(Rational::from_bigint(BigInt::parse_decimal(s)?))
742        }
743    }
744}
745
746impl Ord for Rational {
747    fn cmp(&self, other: &Self) -> Ordering {
748        // a/b vs c/d with b, d > 0: compare a·d vs c·b (no rounding).
749        self.num.mul(&other.den).cmp(&other.num.mul(&self.den))
750    }
751}
752
753impl PartialOrd for Rational {
754    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
755        Some(self.cmp(other))
756    }
757}
758
759impl fmt::Display for Rational {
760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761        if self.is_integer() {
762            write!(f, "{}", self.num)
763        } else {
764            write!(f, "{}/{}", self.num, self.den)
765        }
766    }
767}
768
769impl fmt::Debug for Rational {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        write!(f, "Rational({}/{})", self.num, self.den)
772    }
773}
774
775impl From<i64> for Rational {
776    fn from(x: i64) -> Self {
777        Rational::from_i64(x)
778    }
779}
780
781impl From<BigInt> for Rational {
782    fn from(x: BigInt) -> Self {
783        Rational::from_bigint(x)
784    }
785}
786
787// =====================================================================
788// Decimal — exact base-10 fixed-point on top of BigInt
789// =====================================================================
790
791/// How a [`Decimal`] resolves the digits it must drop when rounding to a smaller
792/// scale. `HalfEven` (banker's rounding) is the money/finance default: the unbiased
793/// tie-break that does not drift a long column of sums upward.
794#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
795pub enum RoundingMode {
796    /// Toward zero (truncate).
797    Down,
798    /// Away from zero.
799    Up,
800    /// Toward −∞.
801    Floor,
802    /// Toward +∞.
803    Ceiling,
804    /// Nearest; ties away from zero.
805    HalfUp,
806    /// Nearest; ties toward zero.
807    HalfDown,
808    /// Nearest; ties to the even neighbour (banker's rounding).
809    HalfEven,
810}
811
812/// `10^k` as a [`BigInt`].
813fn ten_pow(k: u32) -> BigInt {
814    BigInt::from_u64(10).pow(k)
815}
816
817/// Round a rational to the nearest integer under `mode`. The single rounding
818/// primitive: [`Decimal`] scale changes and division both funnel through here, so
819/// every mode behaves identically everywhere.
820fn round_rational_to_bigint(x: &Rational, mode: RoundingMode) -> BigInt {
821    let num = x.numerator();
822    let den = x.denominator(); // INVARIANT (Rational): den > 0.
823    let (q, r) = num.div_rem(den).expect("rational denominator is nonzero");
824    if r.is_zero() {
825        return q; // exact — no rounding to do.
826    }
827    let neg = num.is_negative();
828    // Compare 2·|r| against den to place the fraction relative to the half-way point.
829    let twice = r.abs().mul(&BigInt::from_i64(2));
830    let half = twice.cmp(den);
831    let away = match mode {
832        RoundingMode::Down => false,
833        RoundingMode::Up => true,
834        RoundingMode::Floor => neg,
835        RoundingMode::Ceiling => !neg,
836        RoundingMode::HalfUp => half != Ordering::Less,
837        RoundingMode::HalfDown => half == Ordering::Greater,
838        RoundingMode::HalfEven => half == Ordering::Greater || (half == Ordering::Equal && q.is_odd()),
839    };
840    if away {
841        // `q` is truncated toward zero, so rounding away adds the value's sign.
842        if neg { q.sub(&BigInt::from_i64(1)) } else { q.add(&BigInt::from_i64(1)) }
843    } else {
844        q
845    }
846}
847
848/// An exact base-10 fixed-point number: an integer `coefficient` divided by a power
849/// of ten. The value is `coefficient · 10^(−scale)`, so `19.99` is `coefficient =
850/// 1999`, `scale = 2`. Money lives here — `0.1 + 0.2` is exactly `0.3`, never the
851/// `0.30000000000000004` of a binary float, and a long ledger of sums never drifts.
852///
853/// `+`, `−`, `×` are exact (multiplication adds the scales); `÷` and [`Decimal::rescale`]
854/// round under an explicit [`RoundingMode`] because base-10 division need not
855/// terminate. Equality and ordering are by **value** (`1.0 == 1.00 == 1`); `scale`
856/// is only a display hint. The coefficient is a [`BigInt`], so the value is unbounded
857/// and exact — the i128 fast path is the documented performance follow-up.
858#[derive(Clone)]
859pub struct Decimal {
860    coeff: BigInt,
861    /// Number of base-10 fractional digits (value = `coeff / 10^scale`).
862    scale: u32,
863}
864
865impl Decimal {
866    /// The integer `coeff` at scale 0.
867    pub fn from_bigint(coeff: BigInt) -> Decimal {
868        Decimal { coeff, scale: 0 }
869    }
870
871    pub fn from_i64(x: i64) -> Decimal {
872        Decimal::from_bigint(BigInt::from_i64(x))
873    }
874
875    /// Construct directly from a coefficient and scale (value = `coeff / 10^scale`).
876    pub fn from_coeff_scale(coeff: BigInt, scale: u32) -> Decimal {
877        Decimal { coeff, scale }
878    }
879
880    pub fn zero() -> Decimal {
881        Decimal::from_i64(0)
882    }
883
884    pub fn one() -> Decimal {
885        Decimal::from_i64(1)
886    }
887
888    pub fn scale(&self) -> u32 {
889        self.scale
890    }
891
892    pub fn coefficient(&self) -> &BigInt {
893        &self.coeff
894    }
895
896    pub fn is_zero(&self) -> bool {
897        self.coeff.is_zero()
898    }
899
900    pub fn is_negative(&self) -> bool {
901        self.coeff.is_negative()
902    }
903
904    pub fn negated(&self) -> Decimal {
905        Decimal { coeff: self.coeff.negated(), scale: self.scale }
906    }
907
908    pub fn abs(&self) -> Decimal {
909        Decimal { coeff: self.coeff.abs(), scale: self.scale }
910    }
911
912    /// Exact view as a [`Rational`] (`coeff / 10^scale`) — lossless, the bridge that
913    /// keeps `Decimal` inside the exact tower.
914    pub fn to_rational(&self) -> Rational {
915        Rational::new(self.coeff.clone(), ten_pow(self.scale)).expect("10^scale is nonzero")
916    }
917
918    /// The fixed-point value nearest `value` at the given `scale`, rounded under `mode`.
919    pub fn from_rational(value: &Rational, scale: u32, mode: RoundingMode) -> Decimal {
920        let scaled = value.mul(&Rational::from_bigint(ten_pow(scale)));
921        Decimal { coeff: round_rational_to_bigint(&scaled, mode), scale }
922    }
923
924    /// Restate at a different scale. Widening appends zeros (exact); narrowing drops
925    /// digits and rounds under `mode`.
926    pub fn rescale(&self, scale: u32, mode: RoundingMode) -> Decimal {
927        if scale >= self.scale {
928            Decimal { coeff: self.coeff.mul(&ten_pow(scale - self.scale)), scale }
929        } else {
930            Decimal::from_rational(&self.to_rational(), scale, mode)
931        }
932    }
933
934    /// Bring two values to a common scale (the larger) so coefficients line up.
935    fn aligned(&self, other: &Decimal) -> (BigInt, BigInt, u32) {
936        let s = self.scale.max(other.scale);
937        let a = self.coeff.mul(&ten_pow(s - self.scale));
938        let b = other.coeff.mul(&ten_pow(s - other.scale));
939        (a, b, s)
940    }
941
942    pub fn add(&self, other: &Decimal) -> Decimal {
943        let (a, b, s) = self.aligned(other);
944        Decimal { coeff: a.add(&b), scale: s }
945    }
946
947    pub fn sub(&self, other: &Decimal) -> Decimal {
948        let (a, b, s) = self.aligned(other);
949        Decimal { coeff: a.sub(&b), scale: s }
950    }
951
952    /// Exact product: coefficients multiply, scales add.
953    pub fn mul(&self, other: &Decimal) -> Decimal {
954        Decimal { coeff: self.coeff.mul(&other.coeff), scale: self.scale + other.scale }
955    }
956
957    /// Quotient rounded to `scale` under `mode`. `None` when `other == 0` — base-10
958    /// division need not terminate, so the caller names the precision it wants.
959    pub fn div(&self, other: &Decimal, scale: u32, mode: RoundingMode) -> Option<Decimal> {
960        let q = self.to_rational().div(&other.to_rational())?;
961        Some(Decimal::from_rational(&q, scale, mode))
962    }
963
964    /// Minimal-scale form (trailing zeros stripped) — the canonical value used for
965    /// `Eq`/`Hash` so `1.0`, `1.00`, and `1` are one key.
966    fn normalized(&self) -> (BigInt, u32) {
967        if self.coeff.is_zero() {
968            return (BigInt::zero(), 0);
969        }
970        let ten = BigInt::from_u64(10);
971        let mut c = self.coeff.clone();
972        let mut s = self.scale;
973        while s > 0 {
974            let (q, r) = c.div_rem(&ten).expect("ten is nonzero");
975            if !r.is_zero() {
976                break;
977            }
978            c = q;
979            s -= 1;
980        }
981        (c, s)
982    }
983
984    /// Sign + little-endian coefficient bytes + scale — the wire form (the inverse of
985    /// [`Decimal::from_le_bytes`]). Reuses [`BigInt`]'s exact byte serialization.
986    pub fn to_le_bytes(&self) -> (bool, Vec<u8>, u32) {
987        let (neg, bytes) = self.coeff.to_le_bytes();
988        (neg, bytes, self.scale)
989    }
990
991    pub fn from_le_bytes(negative: bool, bytes: &[u8], scale: u32) -> Decimal {
992        Decimal { coeff: BigInt::from_le_bytes(negative, bytes), scale }
993    }
994
995    /// Parse plain decimal notation: optional sign, optional integer part, optional
996    /// `.fraction`. `"19.99"`, `"-0.005"`, `".5"`, `"5."`, `"42"` all parse; the scale
997    /// is the count of fractional digits. `None` on a non-digit or an empty value.
998    pub fn parse(s: &str) -> Option<Decimal> {
999        let s = s.trim();
1000        let (neg, body) = match s.as_bytes().first() {
1001            Some(b'-') => (true, &s[1..]),
1002            Some(b'+') => (false, &s[1..]),
1003            _ => (false, s),
1004        };
1005        if body.is_empty() {
1006            return None;
1007        }
1008        let (int_part, frac_part) = match body.split_once('.') {
1009            Some((i, f)) => (i, f),
1010            None => (body, ""),
1011        };
1012        if int_part.is_empty() && frac_part.is_empty() {
1013            return None; // a lone "." is not a number.
1014        }
1015        if !int_part.bytes().all(|c| c.is_ascii_digit()) || !frac_part.bytes().all(|c| c.is_ascii_digit()) {
1016            return None;
1017        }
1018        let digits = format!("{int_part}{frac_part}");
1019        let mag = if digits.is_empty() { BigInt::zero() } else { BigInt::parse_decimal(&digits)? };
1020        let coeff = if neg { mag.negated() } else { mag };
1021        Some(Decimal { coeff, scale: frac_part.len() as u32 })
1022    }
1023}
1024
1025impl PartialEq for Decimal {
1026    fn eq(&self, other: &Self) -> bool {
1027        self.normalized() == other.normalized()
1028    }
1029}
1030
1031impl Eq for Decimal {}
1032
1033impl std::hash::Hash for Decimal {
1034    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1035        self.normalized().hash(state);
1036    }
1037}
1038
1039impl Ord for Decimal {
1040    fn cmp(&self, other: &Self) -> Ordering {
1041        // Compare by value (cross-scale, no rounding) via the exact rational view.
1042        self.to_rational().cmp(&other.to_rational())
1043    }
1044}
1045
1046impl PartialOrd for Decimal {
1047    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1048        Some(self.cmp(other))
1049    }
1050}
1051
1052impl fmt::Display for Decimal {
1053    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1054        if self.scale == 0 {
1055            return write!(f, "{}", self.coeff);
1056        }
1057        let neg = self.coeff.is_negative();
1058        let mut digits = self.coeff.abs().to_string();
1059        let scale = self.scale as usize;
1060        if digits.len() <= scale {
1061            // Pad so there is at least one integer digit (e.g. "5" at scale 3 → "0005").
1062            digits = format!("{}{}", "0".repeat(scale + 1 - digits.len()), digits);
1063        }
1064        let point = digits.len() - scale;
1065        if neg {
1066            write!(f, "-")?;
1067        }
1068        write!(f, "{}.{}", &digits[..point], &digits[point..])
1069    }
1070}
1071
1072impl fmt::Debug for Decimal {
1073    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1074        write!(f, "Decimal({self})")
1075    }
1076}
1077
1078impl From<i64> for Decimal {
1079    fn from(x: i64) -> Self {
1080        Decimal::from_i64(x)
1081    }
1082}
1083
1084impl From<BigInt> for Decimal {
1085    fn from(x: BigInt) -> Self {
1086        Decimal::from_bigint(x)
1087    }
1088}
1089
1090// =====================================================================
1091// Complex — exact Gaussian rationals on top of Rational
1092// =====================================================================
1093
1094/// An exact complex number `re + im·i`, each part a [`Rational`]. Because the parts
1095/// are exact, `i·i == −1` and `(1+i)(1−i) == 2` hold with no floating error, and the
1096/// Gaussian rationals are closed under `+ − × ÷` (every nonzero value has an exact
1097/// inverse). The magnitude `√(re²+im²)` is irrational in general — request it as an
1098/// `f64` view via [`Complex::abs_f64`] rather than forcing the exact value inexact.
1099///
1100/// Complex numbers are NOT ordered, so there is deliberately no `Ord`/`PartialOrd`.
1101#[derive(Clone, PartialEq, Eq, Hash)]
1102pub struct Complex {
1103    re: Rational,
1104    im: Rational,
1105}
1106
1107impl Complex {
1108    pub fn new(re: Rational, im: Rational) -> Complex {
1109        Complex { re, im }
1110    }
1111
1112    /// A real number as `re + 0i`.
1113    pub fn from_rational(re: Rational) -> Complex {
1114        Complex { re, im: Rational::zero() }
1115    }
1116
1117    pub fn from_i64(x: i64) -> Complex {
1118        Complex::from_rational(Rational::from_i64(x))
1119    }
1120
1121    pub fn zero() -> Complex {
1122        Complex { re: Rational::zero(), im: Rational::zero() }
1123    }
1124
1125    pub fn one() -> Complex {
1126        Complex::from_i64(1)
1127    }
1128
1129    /// The imaginary unit `i` (`0 + 1i`).
1130    pub fn i() -> Complex {
1131        Complex { re: Rational::zero(), im: Rational::one() }
1132    }
1133
1134    pub fn re(&self) -> &Rational {
1135        &self.re
1136    }
1137
1138    pub fn im(&self) -> &Rational {
1139        &self.im
1140    }
1141
1142    pub fn is_zero(&self) -> bool {
1143        self.re.is_zero() && self.im.is_zero()
1144    }
1145
1146    /// True when the imaginary part is zero (the value is a plain real).
1147    pub fn is_real(&self) -> bool {
1148        self.im.is_zero()
1149    }
1150
1151    /// `re − im·i`.
1152    pub fn conjugate(&self) -> Complex {
1153        Complex { re: self.re.clone(), im: self.im.negated() }
1154    }
1155
1156    /// `re² + im²` — the squared magnitude, exact (a nonnegative [`Rational`]).
1157    pub fn norm_sqr(&self) -> Rational {
1158        self.re.mul(&self.re).add(&self.im.mul(&self.im))
1159    }
1160
1161    /// The magnitude `√(re²+im²)` as an `f64` (lossy by nature — the exact value is
1162    /// generally irrational). The components stay exact; this is a view.
1163    pub fn abs_f64(&self) -> f64 {
1164        self.norm_sqr().to_f64().sqrt()
1165    }
1166
1167    pub fn negated(&self) -> Complex {
1168        Complex { re: self.re.negated(), im: self.im.negated() }
1169    }
1170
1171    pub fn add(&self, other: &Complex) -> Complex {
1172        Complex { re: self.re.add(&other.re), im: self.im.add(&other.im) }
1173    }
1174
1175    pub fn sub(&self, other: &Complex) -> Complex {
1176        Complex { re: self.re.sub(&other.re), im: self.im.sub(&other.im) }
1177    }
1178
1179    /// `(a+bi)(c+di) = (ac − bd) + (ad + bc)i`.
1180    pub fn mul(&self, other: &Complex) -> Complex {
1181        let (a, b, c, d) = (&self.re, &self.im, &other.re, &other.im);
1182        Complex { re: a.mul(c).sub(&b.mul(d)), im: a.mul(d).add(&b.mul(c)) }
1183    }
1184
1185    /// `1/self` via the conjugate over the squared magnitude — `None` only for zero.
1186    pub fn recip(&self) -> Option<Complex> {
1187        let n = self.norm_sqr();
1188        if n.is_zero() {
1189            return None;
1190        }
1191        let inv = n.recip().expect("norm is nonzero here");
1192        Some(Complex { re: self.re.mul(&inv), im: self.im.negated().mul(&inv) })
1193    }
1194
1195    /// `self / other` — `None` when `other == 0`.
1196    pub fn div(&self, other: &Complex) -> Option<Complex> {
1197        Some(self.mul(&other.recip()?))
1198    }
1199
1200    /// `self` raised to an integer power. Negative exponents take the reciprocal
1201    /// first; `None` only for zero raised to a negative power. `0^0 == 1`.
1202    pub fn pow(&self, exp: i32) -> Option<Complex> {
1203        if exp < 0 {
1204            return self.recip()?.pow(-exp);
1205        }
1206        let mut result = Complex::one();
1207        let mut base = self.clone();
1208        let mut e = exp as u32;
1209        while e > 0 {
1210            if e & 1 == 1 {
1211                result = result.mul(&base);
1212            }
1213            e >>= 1;
1214            if e > 0 {
1215                base = base.mul(&base);
1216            }
1217        }
1218        Some(result)
1219    }
1220
1221    /// Parse `"3+4i"`, `"3-4i"`, `"4i"`, `"-i"`, `"i"`, or a bare real `"3"` /`"1/2"`.
1222    /// Round-trips the [`Display`](fmt::Display) form. `None` on malformed input.
1223    pub fn parse(s: &str) -> Option<Complex> {
1224        let s = s.trim();
1225        if s.is_empty() {
1226            return None;
1227        }
1228        let bytes = s.as_bytes();
1229        if bytes[bytes.len() - 1] != b'i' {
1230            // No imaginary part: a plain real.
1231            return Some(Complex::from_rational(Rational::parse(s)?));
1232        }
1233        let body = &s[..s.len() - 1]; // strip trailing 'i'
1234        // Split into real and imaginary at the last sign that is not the leading one.
1235        let split = body
1236            .bytes()
1237            .enumerate()
1238            .rev()
1239            .find(|&(i, c)| i > 0 && (c == b'+' || c == b'-'))
1240            .map(|(i, _)| i);
1241        let (real_str, imag_str) = match split {
1242            Some(i) => (&body[..i], &body[i..]),
1243            None => ("", body),
1244        };
1245        let re = if real_str.is_empty() { Rational::zero() } else { Rational::parse(real_str)? };
1246        let im = match imag_str {
1247            "" | "+" => Rational::one(),
1248            "-" => Rational::from_i64(-1),
1249            other => Rational::parse(other)?,
1250        };
1251        Some(Complex { re, im })
1252    }
1253}
1254
1255impl fmt::Display for Complex {
1256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1257        if self.im.is_zero() {
1258            return write!(f, "{}", self.re);
1259        }
1260        let mag = self.im.abs();
1261        let unit = mag == Rational::one(); // omit the coefficient when it is 1
1262        if self.re.is_zero() {
1263            // Pure imaginary.
1264            return if self.im.is_negative() {
1265                if unit { write!(f, "-i") } else { write!(f, "-{mag}i") }
1266            } else if unit {
1267                write!(f, "i")
1268            } else {
1269                write!(f, "{mag}i")
1270            };
1271        }
1272        let sign = if self.im.is_negative() { "-" } else { "+" };
1273        if unit {
1274            write!(f, "{}{}i", self.re, sign)
1275        } else {
1276            write!(f, "{}{}{}i", self.re, sign, mag)
1277        }
1278    }
1279}
1280
1281impl fmt::Debug for Complex {
1282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1283        write!(f, "Complex({self})")
1284    }
1285}
1286
1287impl From<i64> for Complex {
1288    fn from(x: i64) -> Self {
1289        Complex::from_i64(x)
1290    }
1291}
1292
1293impl From<Rational> for Complex {
1294    fn from(x: Rational) -> Self {
1295        Complex::from_rational(x)
1296    }
1297}
1298
1299// =====================================================================
1300// Modular — the ring ℤ/nℤ over an arbitrary modulus, on top of BigInt
1301// =====================================================================
1302
1303/// Extended Euclid on `BigInt`: returns `(g, x, y)` with `a·x + b·y = g`, `g = gcd(a, b)`.
1304/// The basis for the modular inverse (`g == 1 ⇒ x` is `a⁻¹ mod b`).
1305fn bigint_egcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, BigInt) {
1306    let (mut old_r, mut r) = (a.clone(), b.clone());
1307    let (mut old_s, mut s) = (BigInt::from_i64(1), BigInt::zero());
1308    let (mut old_t, mut t) = (BigInt::zero(), BigInt::from_i64(1));
1309    while !r.is_zero() {
1310        let (q, _) = old_r.div_rem(&r).expect("r is nonzero inside the loop");
1311        let nr = old_r.sub(&q.mul(&r));
1312        old_r = std::mem::replace(&mut r, nr);
1313        let ns = old_s.sub(&q.mul(&s));
1314        old_s = std::mem::replace(&mut s, ns);
1315        let nt = old_t.sub(&q.mul(&t));
1316        old_t = std::mem::replace(&mut t, nt);
1317    }
1318    (old_r, old_s, old_t)
1319}
1320
1321/// An element of the ring ℤ/nℤ — an integer taken modulo a fixed `modulus`. This is the
1322/// arbitrary-modulus generalisation of [`crate::Word8`]/…/[`crate::Word64`] (whose modulus is a power of
1323/// two): arithmetic WRAPS into `[0, modulus)`, the cyclic group where overflow is the point,
1324/// not a bug. The number-theory / crypto substrate — modular exponentiation and the
1325/// extended-Euclid inverse live here, so RSA-style and finite-field code is exact.
1326///
1327/// INVARIANT: `0 ≤ value < modulus` and `modulus ≥ 1` (built via [`Modular::new`], which
1328/// reduces into range), so equal residues share one representation (`Eq`/`Hash` are structural).
1329/// NOT ordered (ℤ/nℤ has no canonical total order).
1330#[derive(Clone, PartialEq, Eq, Hash)]
1331pub struct Modular {
1332    value: BigInt,
1333    modulus: BigInt,
1334}
1335
1336/// Euclidean reduction into `[0, modulus)` (the remainder is made non-negative).
1337fn mod_reduce(value: BigInt, modulus: &BigInt) -> BigInt {
1338    let (_, r) = value.div_rem(modulus).expect("modulus is nonzero");
1339    if r.is_negative() {
1340        r.add(modulus)
1341    } else {
1342        r
1343    }
1344}
1345
1346impl Modular {
1347    /// Reduce `value` into the ring ℤ/`modulus`ℤ. `None` for a non-positive modulus.
1348    pub fn new(value: BigInt, modulus: BigInt) -> Option<Modular> {
1349        if modulus.is_zero() || modulus.is_negative() {
1350            return None;
1351        }
1352        let value = mod_reduce(value, &modulus);
1353        Some(Modular { value, modulus })
1354    }
1355
1356    /// Convenience over machine integers (`None` if `modulus ≤ 0`).
1357    pub fn from_i64(value: i64, modulus: i64) -> Option<Modular> {
1358        Modular::new(BigInt::from_i64(value), BigInt::from_i64(modulus))
1359    }
1360
1361    /// The canonical representative in `[0, modulus)`.
1362    pub fn value(&self) -> &BigInt {
1363        &self.value
1364    }
1365
1366    pub fn modulus(&self) -> &BigInt {
1367        &self.modulus
1368    }
1369
1370    pub fn is_zero(&self) -> bool {
1371        self.value.is_zero()
1372    }
1373
1374    /// `self + other` — `None` when the moduli differ (a ring mismatch, like a Word width mismatch).
1375    pub fn add(&self, other: &Modular) -> Option<Modular> {
1376        if self.modulus != other.modulus {
1377            return None;
1378        }
1379        Modular::new(self.value.add(&other.value), self.modulus.clone())
1380    }
1381
1382    pub fn sub(&self, other: &Modular) -> Option<Modular> {
1383        if self.modulus != other.modulus {
1384            return None;
1385        }
1386        Modular::new(self.value.sub(&other.value), self.modulus.clone())
1387    }
1388
1389    pub fn mul(&self, other: &Modular) -> Option<Modular> {
1390        if self.modulus != other.modulus {
1391            return None;
1392        }
1393        Modular::new(self.value.mul(&other.value), self.modulus.clone())
1394    }
1395
1396    /// The additive inverse `−self` (i.e. `modulus − value`, reduced).
1397    pub fn negated(&self) -> Modular {
1398        Modular::new(self.value.negated(), self.modulus.clone()).expect("modulus stays valid")
1399    }
1400
1401    /// `self^exp` by fast modular exponentiation (square-and-multiply). `0^0 == 1`.
1402    pub fn pow(&self, mut exp: u64) -> Modular {
1403        let one = Modular::new(BigInt::from_i64(1), self.modulus.clone()).expect("modulus valid");
1404        let mut result = one;
1405        let mut base = self.clone();
1406        while exp > 0 {
1407            if exp & 1 == 1 {
1408                result = result.mul(&base).expect("same modulus");
1409            }
1410            exp >>= 1;
1411            if exp > 0 {
1412                base = base.mul(&base).expect("same modulus");
1413            }
1414        }
1415        result
1416    }
1417
1418    /// The multiplicative inverse `self⁻¹` (extended Euclid). `None` unless
1419    /// `gcd(value, modulus) == 1` (the value must be a unit of the ring).
1420    pub fn inverse(&self) -> Option<Modular> {
1421        let (g, x, _) = bigint_egcd(&self.value, &self.modulus);
1422        if g != BigInt::from_i64(1) {
1423            return None;
1424        }
1425        Modular::new(x, self.modulus.clone())
1426    }
1427
1428    /// `self / other = self · other⁻¹` — `None` on a modulus mismatch or a non-invertible divisor.
1429    pub fn div(&self, other: &Modular) -> Option<Modular> {
1430        if self.modulus != other.modulus {
1431            return None;
1432        }
1433        self.mul(&other.inverse()?)
1434    }
1435}
1436
1437impl fmt::Display for Modular {
1438    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1439        write!(f, "{} (mod {})", self.value, self.modulus)
1440    }
1441}
1442
1443impl fmt::Debug for Modular {
1444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1445        write!(f, "Modular({} mod {})", self.value, self.modulus)
1446    }
1447}
1448
1449// ─── Exact float interop ────────────────────────────────────────────────────
1450//
1451// Cross-type numeric comparison in LOGOS is EXACT — mathematical values, never
1452// a lossy cast (`i as f64` rounds above 2^53, so a cast-based `==` would call
1453// `9007199254740993` equal to the float `9007199254740992.0`). Every finite
1454// f64 is exactly `±m · 2^e`, so the exact answer is always computable through
1455// the BigInt/Rational tower. CPython uses the same model.
1456
1457/// The Mersenne prime 2^61 − 1: the modulus of the UNIFIED numeric hash.
1458/// Every numeric type hashes to its mathematical value mod P, so values that
1459/// compare equal across types (`1 == 1.0 == 1/1`) hash equal — the hash/
1460/// equality coherence law that lets mixed-type map keys unify.
1461pub const NUMERIC_HASH_P: u64 = (1u64 << 61) - 1;
1462
1463/// Decompose a finite f64 into `(negative, mantissa, exponent)` with
1464/// value = ±mantissa · 2^exponent (mantissa integral). `None` for NaN/±inf.
1465fn decompose_f64(f: f64) -> Option<(bool, u64, i32)> {
1466    if !f.is_finite() {
1467        return None;
1468    }
1469    let bits = f.to_bits();
1470    let neg = bits >> 63 == 1;
1471    let biased = ((bits >> 52) & 0x7ff) as i32;
1472    let frac = bits & ((1u64 << 52) - 1);
1473    let (m, e) = if biased == 0 {
1474        (frac, -1074) // subnormal (or zero)
1475    } else {
1476        (frac | (1u64 << 52), biased - 1075)
1477    };
1478    Some((neg, m, e))
1479}
1480
1481/// The EXACT rational value of a finite f64. `None` for NaN/±inf.
1482pub fn rational_from_f64_exact(f: f64) -> Option<Rational> {
1483    let (neg, m, e) = decompose_f64(f)?;
1484    let mag = BigInt::from_u64(m);
1485    let two = BigInt::from_u64(2);
1486    let (num, den) = if e >= 0 {
1487        (mag.mul(&two.pow(e as u32)), BigInt::from_i64(1))
1488    } else {
1489        (mag, two.pow((-e) as u32))
1490    };
1491    let num = if neg { num.negated() } else { num };
1492    Rational::new(num, den)
1493}
1494
1495/// Exact comparison of an i64 against an f64. `None` iff `f` is NaN.
1496pub fn cmp_i64_f64_exact(i: i64, f: f64) -> Option<Ordering> {
1497    if f.is_nan() {
1498        return None;
1499    }
1500    if f == f64::INFINITY {
1501        return Some(Ordering::Less);
1502    }
1503    if f == f64::NEG_INFINITY {
1504        return Some(Ordering::Greater);
1505    }
1506    // Fast path: |i| ≤ 2^53 is exactly representable, so the f64 compare IS
1507    // exact (the common case — small ints against floats).
1508    if i.unsigned_abs() <= (1u64 << 53) {
1509        return (i as f64).partial_cmp(&f);
1510    }
1511    let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
1512    Some(Rational::from_i64(i).cmp(&fr))
1513}
1514
1515/// Exact comparison of a BigInt against an f64. `None` iff `f` is NaN.
1516pub fn cmp_bigint_f64_exact(b: &BigInt, f: f64) -> Option<Ordering> {
1517    if f.is_nan() {
1518        return None;
1519    }
1520    if f == f64::INFINITY {
1521        return Some(Ordering::Less);
1522    }
1523    if f == f64::NEG_INFINITY {
1524        return Some(Ordering::Greater);
1525    }
1526    let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
1527    Some(Rational::from_bigint(b.clone()).cmp(&fr))
1528}
1529
1530/// Exact comparison of a Rational against an f64. `None` iff `f` is NaN.
1531pub fn cmp_rational_f64_exact(r: &Rational, f: f64) -> Option<Ordering> {
1532    if f.is_nan() {
1533        return None;
1534    }
1535    if f == f64::INFINITY {
1536        return Some(Ordering::Less);
1537    }
1538    if f == f64::NEG_INFINITY {
1539        return Some(Ordering::Greater);
1540    }
1541    let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
1542    Some(r.cmp(&fr))
1543}
1544
1545/// Reduce a u64 mod P (one Mersenne fold suffices: `x >> 61 ≤ 7`).
1546fn mod_p(x: u64) -> u64 {
1547    let r = (x & NUMERIC_HASH_P) + (x >> 61);
1548    if r >= NUMERIC_HASH_P { r - NUMERIC_HASH_P } else { r }
1549}
1550
1551fn mul_mod_p(a: u64, b: u64) -> u64 {
1552    ((a as u128 * b as u128) % (NUMERIC_HASH_P as u128)) as u64
1553}
1554
1555fn pow_mod_p(mut base: u64, mut exp: u64) -> u64 {
1556    let mut acc = 1u64;
1557    base %= NUMERIC_HASH_P;
1558    while exp > 0 {
1559        if exp & 1 == 1 {
1560            acc = mul_mod_p(acc, base);
1561        }
1562        base = mul_mod_p(base, base);
1563        exp >>= 1;
1564    }
1565    acc
1566}
1567
1568/// Apply the sign to a magnitude hash: negatives map to `P − h` (0 fixed),
1569/// shared by every numeric hasher so equal values agree.
1570fn signed_hash(neg: bool, h: u64) -> u64 {
1571    if neg && h != 0 { NUMERIC_HASH_P - h } else { h }
1572}
1573
1574/// The unified numeric hash of an i64 (its value mod P, sign-adjusted).
1575pub fn numeric_hash_i64(n: i64) -> u64 {
1576    signed_hash(n < 0, mod_p(n.unsigned_abs()))
1577}
1578
1579/// The unified numeric hash of a BigInt. Limbs fold most-significant first:
1580/// 2^64 ≡ 8 (mod 2^61 − 1).
1581pub fn numeric_hash_bigint(b: &BigInt) -> u64 {
1582    let mut acc: u64 = 0;
1583    for &limb in b.mag.limbs().iter().rev() {
1584        let t = (acc as u128) * 8 + (mod_p(limb) as u128);
1585        acc = (t % (NUMERIC_HASH_P as u128)) as u64;
1586    }
1587    signed_hash(b.is_negative(), acc)
1588}
1589
1590/// The unified numeric hash of an f64: `m · 2^e mod P` via modular
1591/// exponentiation (2^61 ≡ 1, so the exponent reduces mod 61). NaN and the
1592/// infinities take fixed sentinels outside the finite-value pattern.
1593pub fn numeric_hash_f64(f: f64) -> u64 {
1594    if f.is_nan() {
1595        return 0x6e616e; // "nan"
1596    }
1597    if f == f64::INFINITY {
1598        return 314159;
1599    }
1600    if f == f64::NEG_INFINITY {
1601        return NUMERIC_HASH_P - 314159;
1602    }
1603    let (neg, m, e) = decompose_f64(f).expect("finite");
1604    let h = mul_mod_p(mod_p(m), pow_mod_p(2, e.rem_euclid(61) as u64));
1605    signed_hash(neg, h)
1606}
1607
1608/// The unified numeric hash of a Rational: `num · den⁻¹ mod P` (Fermat
1609/// inverse; P is prime, and a reduced denominator is never ≡ 0 mod P for
1610/// representable values).
1611pub fn numeric_hash_rational(r: &Rational) -> u64 {
1612    let num = numeric_hash_bigint(&r.numerator().abs());
1613    let den = numeric_hash_bigint(&r.denominator().abs());
1614    let inv = pow_mod_p(den, NUMERIC_HASH_P - 2);
1615    signed_hash(r.is_negative(), mul_mod_p(num, inv))
1616}
1617
1618#[cfg(test)]
1619mod tests {
1620    use super::*;
1621
1622    fn b(x: i64) -> BigInt {
1623        BigInt::from_i64(x)
1624    }
1625
1626    #[test]
1627    fn from_to_i64_round_trips_the_extremes() {
1628        for x in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN, i64::MAX - 1, i64::MIN + 1] {
1629            assert_eq!(BigInt::from_i64(x).to_i64(), Some(x), "round trip {x}");
1630        }
1631    }
1632
1633    #[test]
1634    fn to_i64_is_none_just_past_the_boundary() {
1635        // i64::MAX + 1 and i64::MIN - 1 must NOT fit i64 (this is the whole point —
1636        // the value survives instead of wrapping, unlike a JSON double).
1637        let over = b(i64::MAX).add(&b(1));
1638        let under = b(i64::MIN).sub(&b(1));
1639        assert_eq!(over.to_i64(), None);
1640        assert_eq!(under.to_i64(), None);
1641        // …but the values are still exact and printable.
1642        assert_eq!(over.to_string(), "9223372036854775808");
1643        assert_eq!(under.to_string(), "-9223372036854775809");
1644    }
1645
1646    #[test]
1647    fn add_sub_mul_match_i128_on_a_dense_grid() {
1648        // Differential oracle: for every pair that fits i128, our wide arithmetic must
1649        // equal the machine's. Includes carry/borrow/sign corners.
1650        let xs: [i64; 11] =
1651            [0, 1, -1, 2, -2, 1000, -1000, i32::MAX as i64, i32::MIN as i64, i64::MAX, i64::MIN];
1652        for &x in &xs {
1653            for &y in &xs {
1654                let (bx, by) = (b(x), b(y));
1655                assert_eq!(bx.add(&by).to_string(), (x as i128 + y as i128).to_string(), "{x}+{y}");
1656                assert_eq!(bx.sub(&by).to_string(), (x as i128 - y as i128).to_string(), "{x}-{y}");
1657                assert_eq!(bx.mul(&by).to_string(), (x as i128 * y as i128).to_string(), "{x}*{y}");
1658            }
1659        }
1660    }
1661
1662    #[test]
1663    fn div_rem_matches_i64_truncation_including_signs() {
1664        let xs = [0i64, 1, -1, 7, -7, 100, -100, 9_999_999, -9_999_999, i64::MAX, i64::MIN];
1665        let ys = [1i64, -1, 2, -2, 3, -3, 7, -7, 1000, -1000];
1666        for &x in &xs {
1667            for &y in &ys {
1668                let (q, r) = b(x).div_rem(&b(y)).expect("nonzero divisor");
1669                // i64::MIN / -1 overflows i64; compare in i128 there.
1670                let (eq, er) = ((x as i128) / (y as i128), (x as i128) % (y as i128));
1671                assert_eq!(q.to_string(), eq.to_string(), "{x}/{y} quotient");
1672                assert_eq!(r.to_string(), er.to_string(), "{x}%{y} remainder");
1673                // The defining identity must hold exactly.
1674                assert_eq!(b(x), q.mul(&b(y)).add(&r), "x = q*y + r for {x},{y}");
1675            }
1676        }
1677    }
1678
1679    #[test]
1680    fn division_by_zero_is_none_not_a_panic() {
1681        assert!(b(5).div_rem(&BigInt::zero()).is_none());
1682        assert!(BigInt::zero().div_rem(&BigInt::zero()).is_none());
1683    }
1684
1685    #[test]
1686    fn huge_factorial_is_exact() {
1687        // 50! has 65 digits — far beyond any fixed-width integer or f64.
1688        let mut acc = BigInt::from_u64(1);
1689        for k in 1..=50u64 {
1690            acc = acc.mul(&BigInt::from_u64(k));
1691        }
1692        assert_eq!(acc.to_string(), "30414093201713378043612608166064768844377641568960512000000000000");
1693        // Dividing back down the chain returns exactly 1 (exercises big/big division).
1694        for k in 1..=50u64 {
1695            let (q, r) = acc.div_rem(&BigInt::from_u64(k)).unwrap();
1696            assert!(r.is_zero(), "{k}! divides cleanly");
1697            acc = q;
1698        }
1699        assert_eq!(acc, BigInt::from_u64(1));
1700    }
1701
1702    #[test]
1703    fn parse_and_display_round_trip_big_decimals() {
1704        for s in [
1705            "0",
1706            "-0",
1707            "7",
1708            "-7",
1709            "9223372036854775808",
1710            "-9223372036854775809",
1711            "123456789012345678901234567890",
1712            "-100000000000000000000000000000000000000000",
1713        ] {
1714            let parsed = BigInt::parse_decimal(s).expect("parse");
1715            // "-0" canonicalizes to "0".
1716            let expected = if s == "-0" { "0" } else { s };
1717            assert_eq!(parsed.to_string(), expected, "round trip {s}");
1718        }
1719        assert!(BigInt::parse_decimal("12x3").is_none());
1720        assert!(BigInt::parse_decimal("").is_none());
1721        assert!(BigInt::parse_decimal("-").is_none());
1722    }
1723
1724    /// A tiny deterministic RNG (SplitMix64) so the fuzz is reproducible with no
1725    /// external dependency.
1726    struct Rng(u64);
1727    impl Rng {
1728        fn next(&mut self) -> u64 {
1729            self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
1730            let mut z = self.0;
1731            z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1732            z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1733            z ^ (z >> 31)
1734        }
1735        /// A random BigInt of 0..=3 limbs and random sign — spans single- and
1736        /// multi-limb magnitudes (and zero).
1737        fn big(&mut self) -> BigInt {
1738            let limbs = (self.next() % 4) as usize;
1739            let mut bytes = Vec::new();
1740            for _ in 0..limbs {
1741                bytes.extend_from_slice(&self.next().to_le_bytes());
1742            }
1743            BigInt::from_le_bytes(self.next() & 1 == 1, &bytes)
1744        }
1745    }
1746
1747    #[test]
1748    fn fuzz_algebraic_laws_hold_for_random_bigints() {
1749        let mut r = Rng(0x0BAD_F00D_DEAD_BEEF);
1750        for _ in 0..3000 {
1751            let (a, b, c) = (r.big(), r.big(), r.big());
1752            // Commutativity.
1753            assert_eq!(a.add(&b), b.add(&a), "add commutes");
1754            assert_eq!(a.mul(&b), b.mul(&a), "mul commutes");
1755            // Associativity.
1756            assert_eq!(a.add(&b).add(&c), a.add(&b.add(&c)), "add associates");
1757            assert_eq!(a.mul(&b).mul(&c), a.mul(&b.mul(&c)), "mul associates");
1758            // Distributivity.
1759            assert_eq!(a.mul(&b.add(&c)), a.mul(&b).add(&a.mul(&c)), "mul distributes over add");
1760            // Identities and inverses.
1761            assert_eq!(a.add(&BigInt::zero()), a, "0 is the additive identity");
1762            assert_eq!(a.mul(&BigInt::from_u64(1)), a, "1 is the multiplicative identity");
1763            assert!(a.mul(&BigInt::zero()).is_zero(), "x*0 = 0");
1764            assert_eq!(a.sub(&a), BigInt::zero(), "x - x = 0");
1765            assert_eq!(a.add(&a.negated()), BigInt::zero(), "x + (-x) = 0");
1766            assert_eq!(a.negated().negated(), a, "double negation");
1767            // abs is non-negative; sign of a product.
1768            assert!(!a.abs().is_negative(), "|x| >= 0");
1769            assert_eq!(a.negated().mul(&b), a.mul(&b).negated(), "(-a)*b = -(a*b)");
1770            // Division identity and remainder bound.
1771            if !b.is_zero() {
1772                let (q, rem) = a.div_rem(&b).unwrap();
1773                assert_eq!(q.mul(&b).add(&rem), a, "a = q*b + r exactly");
1774                assert!(rem.is_zero() || rem.abs() < b.abs(), "|r| < |b|");
1775            }
1776            // Serialization and decimal round-trips.
1777            let (neg, bytes) = a.to_le_bytes();
1778            assert_eq!(BigInt::from_le_bytes(neg, &bytes), a, "byte round-trip");
1779            assert_eq!(BigInt::parse_decimal(&a.to_string()).unwrap(), a, "decimal round-trip");
1780            // Ordering is a total, antisymmetric, transitive relation.
1781            assert_eq!(a < b, b > a, "antisymmetry");
1782            if a < b && b < c {
1783                assert!(a < c, "transitivity");
1784            }
1785        }
1786    }
1787
1788    #[test]
1789    fn fuzz_differential_against_i128() {
1790        // For random i64 operands, our (promoting) arithmetic must equal i128 exactly.
1791        let mut r = Rng(0xC0FF_EE00_1234_5678);
1792        for _ in 0..5000 {
1793            let x = r.next() as i64;
1794            let y = r.next() as i64;
1795            assert_eq!(b(x).add(&b(y)).to_string(), (x as i128 + y as i128).to_string(), "{x}+{y}");
1796            assert_eq!(b(x).sub(&b(y)).to_string(), (x as i128 - y as i128).to_string(), "{x}-{y}");
1797            assert_eq!(b(x).mul(&b(y)).to_string(), (x as i128 * y as i128).to_string(), "{x}*{y}");
1798            if y != 0 {
1799                let (q, rem) = b(x).div_rem(&b(y)).unwrap();
1800                assert_eq!(q.to_string(), (x as i128 / y as i128).to_string(), "{x}/{y}");
1801                assert_eq!(rem.to_string(), (x as i128 % y as i128).to_string(), "{x}%{y}");
1802            }
1803        }
1804    }
1805
1806    #[test]
1807    fn limb_boundary_edge_cases_are_exact() {
1808        let two64 = BigInt::parse_decimal("18446744073709551616").unwrap(); // 2^64
1809        // 2^64 - 1 = u64::MAX (a borrow that empties the high limb).
1810        assert_eq!(two64.sub(&BigInt::from_u64(1)).to_string(), "18446744073709551615");
1811        // 2^64 * 2^64 = 2^128 (exact two-limb product).
1812        let two128 = two64.mul(&two64);
1813        assert_eq!(two128.to_string(), "340282366920938463463374607431768211456");
1814        // 2^128 - 1 (a borrow chain across every limb).
1815        assert_eq!(two128.sub(&BigInt::from_u64(1)).to_string(), "340282366920938463463374607431768211455");
1816        // u64::MAX + 1 = 2^64 (a carry that grows a limb).
1817        assert_eq!(BigInt::from_u64(u64::MAX).add(&BigInt::from_u64(1)), two64);
1818        // Division with a multi-limb divisor: 2^128 / 2^64 = 2^64, remainder 0.
1819        let (q, rem) = two128.div_rem(&two64).unwrap();
1820        assert_eq!(q, two64);
1821        assert!(rem.is_zero());
1822    }
1823
1824    #[test]
1825    fn pow_is_exact_and_unbounded() {
1826        assert_eq!(b(2).pow(63).to_string(), "9223372036854775808"); // the value i64 can't hold
1827        assert_eq!(b(2).pow(100).to_string(), "1267650600228229401496703205376");
1828        assert_eq!(b(10).pow(0).to_string(), "1");
1829        assert_eq!(b(0).pow(0).to_string(), "1");
1830        assert_eq!(b(-3).pow(3).to_string(), "-27");
1831        assert_eq!(b(-2).pow(10).to_string(), "1024");
1832        // 3^50 cross-checked against repeated multiplication.
1833        let mut acc = BigInt::from_u64(1);
1834        for _ in 0..50 {
1835            acc = acc.mul(&b(3));
1836        }
1837        assert_eq!(b(3).pow(50), acc);
1838    }
1839
1840    #[test]
1841    fn ordering_is_total_and_sign_aware() {
1842        let mut v = vec![b(3), b(-5), BigInt::zero(), b(i64::MAX), b(i64::MIN), b(-5).mul(&b(i64::MAX))];
1843        v.sort();
1844        let as_str: Vec<String> = v.iter().map(|x| x.to_string()).collect();
1845        assert_eq!(
1846            as_str,
1847            vec![
1848                "-46116860184273879035", // -5 * i64::MAX
1849                "-9223372036854775808",  // i64::MIN
1850                "-5",
1851                "0",
1852                "3",
1853                "9223372036854775807", // i64::MAX
1854            ]
1855        );
1856    }
1857
1858    // -----------------------------------------------------------------
1859    // Rational
1860    // -----------------------------------------------------------------
1861
1862    fn r(n: i64, d: i64) -> Rational {
1863        Rational::from_ratio_i64(n, d).expect("nonzero denominator in test")
1864    }
1865
1866    #[test]
1867    fn rational_reduces_to_lowest_terms_on_construction() {
1868        assert_eq!(r(6, 8).to_string(), "3/4");
1869        assert_eq!(r(10, 5).to_string(), "2");
1870        assert_eq!(r(0, 7).to_string(), "0");
1871        assert_eq!(r(100, 1000).to_string(), "1/10");
1872        // The reduced form is canonical, so equal values are structurally equal.
1873        assert_eq!(r(6, 8), r(3, 4));
1874        assert_eq!(r(0, 7), r(0, 1));
1875    }
1876
1877    #[test]
1878    fn rational_normalizes_sign_onto_a_positive_denominator() {
1879        assert_eq!(r(1, -2).to_string(), "-1/2");
1880        assert_eq!(r(-1, -2).to_string(), "1/2");
1881        assert_eq!(r(-3, 4), r(3, -4));
1882        assert!(r(1, -2).is_negative());
1883        assert!(!r(-1, -2).is_negative());
1884        // The denominator accessor is always positive.
1885        assert!(!r(1, -2).denominator().is_negative());
1886    }
1887
1888    #[test]
1889    fn rational_zero_denominator_is_none() {
1890        assert!(Rational::from_ratio_i64(5, 0).is_none());
1891        assert!(Rational::new(BigInt::from_i64(1), BigInt::zero()).is_none());
1892        assert!(r(3, 4).div(&Rational::zero()).is_none());
1893        assert!(Rational::zero().recip().is_none());
1894        assert!(Rational::parse("7/0").is_none());
1895    }
1896
1897    #[test]
1898    fn rational_arithmetic_is_exact() {
1899        assert_eq!(r(1, 3).add(&r(1, 6)).to_string(), "1/2");
1900        assert_eq!(r(1, 2).sub(&r(1, 3)).to_string(), "1/6");
1901        assert_eq!(r(2, 3).mul(&r(3, 4)).to_string(), "1/2");
1902        assert_eq!(r(1, 2).div(&r(3, 4)).unwrap().to_string(), "2/3");
1903        // a + (-a) == 0, a * (1/a) == 1.
1904        assert!(r(5, 7).add(&r(5, 7).negated()).is_zero());
1905        assert_eq!(r(5, 7).mul(&r(5, 7).recip().unwrap()), Rational::one());
1906    }
1907
1908    #[test]
1909    fn one_third_stays_exact_where_a_json_double_would_round() {
1910        // The whole point: 1/3 is EXACT, not 0.3333…; three of them are exactly 1.
1911        let third = r(1, 3);
1912        let sum = third.add(&third).add(&third);
1913        assert_eq!(sum, Rational::one());
1914        assert_eq!(third.to_string(), "1/3");
1915        // The classic f64 trap 0.1 + 0.2 != 0.3 — Rationals don't have it.
1916        assert_ne!(0.1_f64 + 0.2, 0.3);
1917        assert_eq!(r(1, 10).add(&r(2, 10)), r(3, 10));
1918    }
1919
1920    #[test]
1921    fn rational_ordering_cross_multiplies_without_rounding() {
1922        assert!(r(1, 3) < r(1, 2));
1923        assert!(r(-1, 2) < r(1, 3));
1924        assert!(r(2, 4) == r(1, 2));
1925        let mut v = vec![r(1, 2), r(1, 3), r(-1, 4), r(2, 3), Rational::zero(), r(1, 2)];
1926        v.sort();
1927        let s: Vec<String> = v.iter().map(|x| x.to_string()).collect();
1928        assert_eq!(s, ["-1/4", "0", "1/3", "1/2", "1/2", "2/3"]);
1929    }
1930
1931    #[test]
1932    fn rational_terms_overflow_i64_into_bigint() {
1933        // (1/i64::MAX) + (1/i64::MAX) = 2/i64::MAX — denominator past i64 stays exact.
1934        let big = Rational::new(BigInt::from_i64(1), BigInt::from_i64(i64::MAX)).unwrap();
1935        let twice = big.add(&big);
1936        assert_eq!(twice.numerator().to_string(), "2");
1937        assert_eq!(twice.denominator().to_string(), i64::MAX.to_string());
1938        // A product whose numerator escapes i64 is still exact.
1939        let p = r(i64::MAX, 1).mul(&r(3, 1));
1940        assert_eq!(p.numerator().to_i64(), None);
1941        assert_eq!(p.numerator().to_string(), (i64::MAX as i128 * 3).to_string());
1942    }
1943
1944    #[test]
1945    fn rational_pow_handles_negative_and_zero_exponents() {
1946        assert_eq!(r(2, 3).pow(3).unwrap().to_string(), "8/27");
1947        assert_eq!(r(2, 3).pow(0).unwrap(), Rational::one());
1948        assert_eq!(r(2, 3).pow(-2).unwrap().to_string(), "9/4");
1949        assert_eq!(r(-2, 3).pow(-3).unwrap().to_string(), "-27/8");
1950        assert!(Rational::zero().pow(-1).is_none());
1951        assert_eq!(Rational::zero().pow(3).unwrap(), Rational::zero());
1952    }
1953
1954    #[test]
1955    fn rational_parse_round_trips_and_rejects_garbage() {
1956        assert_eq!(Rational::parse("3/4").unwrap().to_string(), "3/4");
1957        assert_eq!(Rational::parse("-3/4").unwrap().to_string(), "-3/4");
1958        assert_eq!(Rational::parse("6/8").unwrap().to_string(), "3/4");
1959        assert_eq!(Rational::parse("5").unwrap().to_string(), "5");
1960        assert_eq!(Rational::parse("  7 / 14 ").unwrap().to_string(), "1/2");
1961        assert!(Rational::parse("abc").is_none());
1962        assert!(Rational::parse("1/2/3").is_none());
1963    }
1964
1965    #[test]
1966    fn rational_integer_predicate_and_narrowing() {
1967        assert!(r(10, 2).is_integer());
1968        assert_eq!(r(10, 2).to_i64(), Some(5));
1969        assert_eq!(r(10, 2).to_bigint().unwrap().to_string(), "5");
1970        assert!(!r(3, 4).is_integer());
1971        assert_eq!(r(3, 4).to_i64(), None);
1972        assert!(r(3, 4).to_bigint().is_none());
1973    }
1974
1975    #[test]
1976    fn rational_floor_and_ceil_round_toward_neg_and_pos_infinity() {
1977        assert_eq!(r(7, 2).floor().to_string(), "3");
1978        assert_eq!(r(7, 2).ceil().to_string(), "4");
1979        assert_eq!(r(-7, 2).floor().to_string(), "-4");
1980        assert_eq!(r(-7, 2).ceil().to_string(), "-3");
1981        // Whole values floor/ceil to themselves.
1982        assert_eq!(r(6, 2).floor().to_string(), "3");
1983        assert_eq!(r(6, 2).ceil().to_string(), "3");
1984        // round ties away from zero, matching f64::round.
1985        assert_eq!(r(7, 2).round().to_string(), "4");
1986        assert_eq!(r(-7, 2).round().to_string(), "-4");
1987        assert_eq!(r(1, 3).round().to_string(), "0");
1988        assert_eq!(r(2, 3).round().to_string(), "1");
1989        // Differential vs f64 on a dense grid (small terms are exact in f64).
1990        for n in -9i64..=9 {
1991            for d in 1i64..=9 {
1992                let q = r(n, d);
1993                assert_eq!(q.floor().to_i64(), Some((n as f64 / d as f64).floor() as i64), "{n}/{d} floor");
1994                assert_eq!(q.ceil().to_i64(), Some((n as f64 / d as f64).ceil() as i64), "{n}/{d} ceil");
1995                assert_eq!(q.round().to_i64(), Some((n as f64 / d as f64).round() as i64), "{n}/{d} round");
1996            }
1997        }
1998    }
1999
2000    #[test]
2001    fn rational_to_f64_matches_the_division_on_small_terms() {
2002        for n in -8i64..=8 {
2003            for d in 1i64..=8 {
2004                let approx = r(n, d).to_f64();
2005                assert!((approx - (n as f64 / d as f64)).abs() < 1e-12, "{n}/{d}");
2006            }
2007        }
2008    }
2009
2010    #[test]
2011    fn rational_obeys_the_field_laws_over_random_fractions() {
2012        // Differential/property fuzz: build fractions from random i64 terms and
2013        // check the field axioms exactly (no rounding, unlike f64).
2014        let mut rng = Rng(0x5A7F_104E_2C19_8B63);
2015        for _ in 0..2000 {
2016            let pick = |rng: &mut Rng| -> i64 { (rng.next() % 4001) as i64 - 2000 };
2017            let (a, b, c, d, e, g) = (pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng));
2018            let (Some(x), Some(y), Some(z)) =
2019                (Rational::from_ratio_i64(a, b.max(1)), Rational::from_ratio_i64(c, d.max(1)), Rational::from_ratio_i64(e, g.max(1)))
2020            else { continue };
2021            // commutativity
2022            assert_eq!(x.add(&y), y.add(&x));
2023            assert_eq!(x.mul(&y), y.mul(&x));
2024            // associativity
2025            assert_eq!(x.add(&y).add(&z), x.add(&y.add(&z)));
2026            assert_eq!(x.mul(&y).mul(&z), x.mul(&y.mul(&z)));
2027            // distributivity
2028            assert_eq!(x.mul(&y.add(&z)), x.mul(&y).add(&x.mul(&z)));
2029            // additive inverse + subtraction agreement
2030            assert!(x.add(&x.negated()).is_zero());
2031            assert_eq!(x.sub(&y), x.add(&y.negated()));
2032            // multiplicative inverse (when nonzero)
2033            if !x.is_zero() {
2034                assert_eq!(x.mul(&x.recip().unwrap()), Rational::one());
2035                assert_eq!(y.div(&x).unwrap(), y.mul(&x.recip().unwrap()));
2036            }
2037        }
2038    }
2039
2040    // -----------------------------------------------------------------
2041    // Decimal — exact base-10 fixed-point
2042    // -----------------------------------------------------------------
2043
2044    fn dec(s: &str) -> Decimal {
2045        Decimal::parse(s).unwrap_or_else(|| panic!("parse decimal {s:?}"))
2046    }
2047
2048    #[test]
2049    fn decimal_parse_and_display_round_trip() {
2050        for s in ["0", "19.99", "0.05", "-0.005", "1999", "100.00", "-7", "0.10", "3.14159"] {
2051            assert_eq!(dec(s).to_string(), s, "round trip {s}");
2052        }
2053        // "-0" and "+5" normalize on display.
2054        assert_eq!(dec("-0").to_string(), "0");
2055        assert_eq!(dec("+5").to_string(), "5");
2056        // leading/trailing forms.
2057        assert_eq!(dec(".5").to_string(), "0.5");
2058        assert_eq!(dec("5.").to_string(), "5");
2059        // garbage rejected.
2060        assert!(Decimal::parse("1.2.3").is_none());
2061        assert!(Decimal::parse("abc").is_none());
2062        assert!(Decimal::parse("").is_none());
2063        assert!(Decimal::parse("-").is_none());
2064        assert!(Decimal::parse(".").is_none());
2065    }
2066
2067    #[test]
2068    fn decimal_has_no_binary_float_drift() {
2069        // The headline: 0.1 + 0.2 == 0.3 EXACTLY (the classic f64 trap), and is shown so.
2070        assert_ne!(0.1_f64 + 0.2, 0.3);
2071        assert_eq!(dec("0.1").add(&dec("0.2")), dec("0.3"));
2072        assert_eq!(dec("0.1").add(&dec("0.2")).to_string(), "0.3");
2073    }
2074
2075    #[test]
2076    fn decimal_add_sub_align_scales() {
2077        assert_eq!(dec("19.99").add(&dec("0.01")).to_string(), "20.00");
2078        assert_eq!(dec("1.1").add(&dec("2.22")).to_string(), "3.32");
2079        assert_eq!(dec("5").add(&dec("0.5")).to_string(), "5.5");
2080        assert_eq!(dec("20.00").sub(&dec("0.01")).to_string(), "19.99");
2081        assert_eq!(dec("0").sub(&dec("0.005")).to_string(), "-0.005");
2082    }
2083
2084    #[test]
2085    fn decimal_mul_adds_scales_and_is_exact() {
2086        assert_eq!(dec("1.1").mul(&dec("1.1")).to_string(), "1.21");
2087        assert_eq!(dec("0.05").mul(&dec("0.05")).to_string(), "0.0025");
2088        assert_eq!(dec("19.99").mul(&Decimal::from_i64(3)).to_string(), "59.97");
2089        assert_eq!(dec("-2.5").mul(&dec("4")).to_string(), "-10.0");
2090    }
2091
2092    #[test]
2093    fn decimal_div_rounds_to_scale() {
2094        // 10/3 = 3.333… → 3.33 at scale 2.
2095        assert_eq!(dec("10").div(&dec("3"), 2, RoundingMode::HalfEven).unwrap().to_string(), "3.33");
2096        // 1/8 = 0.125 exact at scale 3.
2097        assert_eq!(dec("1").div(&dec("8"), 3, RoundingMode::HalfEven).unwrap().to_string(), "0.125");
2098        // At scale 2 the tie 0.125 splits by mode: HalfEven → 0.12, HalfUp → 0.13.
2099        assert_eq!(dec("1").div(&dec("8"), 2, RoundingMode::HalfEven).unwrap().to_string(), "0.12");
2100        assert_eq!(dec("1").div(&dec("8"), 2, RoundingMode::HalfUp).unwrap().to_string(), "0.13");
2101        // Divide by zero is None, never a panic.
2102        assert!(dec("1").div(&dec("0"), 2, RoundingMode::HalfEven).is_none());
2103    }
2104
2105    #[test]
2106    fn decimal_rescale_quantizes_with_rounding() {
2107        assert_eq!(dec("19.999").rescale(2, RoundingMode::HalfEven).to_string(), "20.00");
2108        // Banker's rounding: 2.5 → 2, 3.5 → 4, -2.5 → -2 (ties to even).
2109        assert_eq!(dec("2.5").rescale(0, RoundingMode::HalfEven).to_string(), "2");
2110        assert_eq!(dec("3.5").rescale(0, RoundingMode::HalfEven).to_string(), "4");
2111        assert_eq!(dec("-2.5").rescale(0, RoundingMode::HalfEven).to_string(), "-2");
2112        // Widening just appends zeros (exact).
2113        assert_eq!(dec("1.5").rescale(4, RoundingMode::HalfEven).to_string(), "1.5000");
2114    }
2115
2116    #[test]
2117    fn decimal_rounding_modes_on_a_tie_and_a_non_tie() {
2118        // 2.5 (an exact tie) under every mode.
2119        let tie = dec("2.5");
2120        for (mode, want) in [
2121            (RoundingMode::Down, "2"),
2122            (RoundingMode::Up, "3"),
2123            (RoundingMode::Floor, "2"),
2124            (RoundingMode::Ceiling, "3"),
2125            (RoundingMode::HalfUp, "3"),
2126            (RoundingMode::HalfDown, "2"),
2127            (RoundingMode::HalfEven, "2"),
2128        ] {
2129            assert_eq!(tie.rescale(0, mode).to_string(), want, "2.5 under {mode:?}");
2130        }
2131        // -2.5 (negative tie): Floor → -3, Ceiling → -2, HalfUp → -3.
2132        let ntie = dec("-2.5");
2133        assert_eq!(ntie.rescale(0, RoundingMode::Floor).to_string(), "-3");
2134        assert_eq!(ntie.rescale(0, RoundingMode::Ceiling).to_string(), "-2");
2135        assert_eq!(ntie.rescale(0, RoundingMode::HalfUp).to_string(), "-3");
2136        assert_eq!(ntie.rescale(0, RoundingMode::Down).to_string(), "-2");
2137    }
2138
2139    #[test]
2140    fn decimal_equality_is_value_based_and_hash_agrees() {
2141        use std::collections::HashSet;
2142        // 1.0 == 1.00 == 1 by VALUE; scale is only a display hint.
2143        assert_eq!(dec("1.0"), dec("1.00"));
2144        assert_eq!(dec("1.0"), dec("1"));
2145        assert_eq!(dec("0.0"), dec("0"));
2146        // Equal values must hash equal (the Eq/Hash contract).
2147        let mut set = HashSet::new();
2148        set.insert(dec("1.00"));
2149        assert!(set.contains(&dec("1")));
2150        assert!(set.contains(&dec("1.0")));
2151        // Different values are not equal.
2152        assert_ne!(dec("1.0"), dec("1.01"));
2153    }
2154
2155    #[test]
2156    fn decimal_ordering_compares_across_scales_without_rounding() {
2157        assert!(dec("0.1") < dec("0.11"));
2158        assert!(dec("1.5") < dec("2"));
2159        assert!(dec("-0.005") < dec("0"));
2160        assert!(dec("1.00") == dec("1.0"));
2161        let mut v = vec![dec("0.5"), dec("0.05"), dec("-0.1"), dec("2"), dec("0.50")];
2162        v.sort();
2163        let s: Vec<String> = v.iter().map(|x| x.to_string()).collect();
2164        // 0.5 and 0.50 are equal; sort is stable so input order among equals is kept.
2165        assert_eq!(s, ["-0.1", "0.05", "0.5", "0.50", "2"]);
2166    }
2167
2168    #[test]
2169    fn decimal_to_and_from_rational_is_exact() {
2170        assert_eq!(dec("19.99").to_rational(), Rational::from_ratio_i64(1999, 100).unwrap());
2171        assert_eq!(dec("0.125").to_rational(), Rational::from_ratio_i64(1, 8).unwrap());
2172        assert_eq!(dec("-0.005").to_rational(), Rational::from_ratio_i64(-1, 200).unwrap());
2173        // from_rational rounds to the requested scale.
2174        let third = Rational::from_ratio_i64(1, 3).unwrap();
2175        assert_eq!(Decimal::from_rational(&third, 4, RoundingMode::HalfEven).to_string(), "0.3333");
2176    }
2177
2178    #[test]
2179    fn decimal_wire_components_round_trip() {
2180        for s in ["0", "19.99", "-0.005", "100.00", "123456789.000001", "-7"] {
2181            let d = dec(s);
2182            let (neg, bytes, scale) = d.to_le_bytes();
2183            let back = Decimal::from_le_bytes(neg, &bytes, scale);
2184            assert_eq!(back, d, "value round-trip {s}");
2185            assert_eq!(back.to_string(), d.to_string(), "display round-trip {s}");
2186        }
2187    }
2188
2189    #[test]
2190    fn decimal_money_scenario_is_exact() {
2191        // 3 items at $19.99, plus 8% tax rounded to cents (banker's).
2192        let subtotal = dec("19.99").mul(&Decimal::from_i64(3)); // 59.97
2193        assert_eq!(subtotal.to_string(), "59.97");
2194        let tax = subtotal.mul(&dec("0.08")).rescale(2, RoundingMode::HalfEven); // 4.7976 → 4.80
2195        assert_eq!(tax.to_string(), "4.80");
2196        assert_eq!(subtotal.add(&tax).to_string(), "64.77");
2197    }
2198
2199    #[test]
2200    fn decimal_add_sub_mul_match_the_rational_oracle_under_fuzz() {
2201        // Differential: build decimals from random coeff/scale and check that +,-,*
2202        // agree EXACTLY with the Rational tower (the exactness oracle).
2203        let mut rng = Rng(0xDEC1_2A1B_0000_FACE);
2204        for _ in 0..3000 {
2205            let ca = (rng.next() % 2_000_001) as i64 - 1_000_000;
2206            let cb = (rng.next() % 2_000_001) as i64 - 1_000_000;
2207            let sa = (rng.next() % 6) as u32;
2208            let sb = (rng.next() % 6) as u32;
2209            let a = Decimal::from_coeff_scale(BigInt::from_i64(ca), sa);
2210            let b = Decimal::from_coeff_scale(BigInt::from_i64(cb), sb);
2211            let (ra, rb) = (a.to_rational(), b.to_rational());
2212            assert_eq!(a.add(&b).to_rational(), ra.add(&rb), "add {a} {b}");
2213            assert_eq!(a.sub(&b).to_rational(), ra.sub(&rb), "sub {a} {b}");
2214            assert_eq!(a.mul(&b).to_rational(), ra.mul(&rb), "mul {a} {b}");
2215            // div is rounded: its result must be within one ULP of the requested scale.
2216            if !b.is_zero() {
2217                let scale = 8u32;
2218                let q = a.div(&b, scale, RoundingMode::HalfEven).unwrap();
2219                let exact = ra.div(&rb).unwrap();
2220                let ulp = Rational::new(BigInt::from_i64(1), BigInt::from_u64(10).pow(scale)).unwrap();
2221                let err = q.to_rational().sub(&exact).abs();
2222                // |rounded - exact| <= ulp/2 for nearest-rounding.
2223                assert!(err.mul(&Rational::from_i64(2)) <= ulp, "div within half-ulp {a}/{b}");
2224            }
2225        }
2226    }
2227
2228    // -----------------------------------------------------------------
2229    // Complex — exact Gaussian rationals
2230    // -----------------------------------------------------------------
2231
2232    fn c(re: i64, im: i64) -> Complex {
2233        Complex::new(Rational::from_i64(re), Rational::from_i64(im))
2234    }
2235
2236    fn cq(rn: i64, rd: i64, in_: i64, id: i64) -> Complex {
2237        Complex::new(Rational::from_ratio_i64(rn, rd).unwrap(), Rational::from_ratio_i64(in_, id).unwrap())
2238    }
2239
2240    #[test]
2241    fn complex_i_squared_is_minus_one() {
2242        // The headline: i·i == −1, EXACTLY.
2243        assert_eq!(Complex::i().mul(&Complex::i()), c(-1, 0));
2244        assert_eq!(Complex::i().mul(&Complex::i()), Complex::from_i64(-1));
2245    }
2246
2247    #[test]
2248    fn complex_construction_and_accessors() {
2249        let z = c(3, 4);
2250        assert_eq!(z.re(), &Rational::from_i64(3));
2251        assert_eq!(z.im(), &Rational::from_i64(4));
2252        assert!(!z.is_real());
2253        assert!(c(5, 0).is_real());
2254        assert!(Complex::zero().is_zero());
2255        assert!(!Complex::i().is_zero());
2256        assert_eq!(Complex::from_i64(7), c(7, 0));
2257    }
2258
2259    #[test]
2260    fn complex_add_sub_mul_are_exact() {
2261        assert_eq!(c(2, 3).add(&c(1, -1)), c(3, 2));
2262        assert_eq!(c(2, 3).sub(&c(1, -1)), c(1, 4));
2263        // (1+i)(1−i) = 1 − i² = 2 — the classic exact identity.
2264        assert_eq!(c(1, 1).mul(&c(1, -1)), c(2, 0));
2265        // (2+3i)(4+5i) = 8 + 10i + 12i + 15i² = (8−15) + 22i = −7 + 22i.
2266        assert_eq!(c(2, 3).mul(&c(4, 5)), c(-7, 22));
2267    }
2268
2269    #[test]
2270    fn complex_div_recip_and_zero_guard() {
2271        // z/z == 1 for any nonzero z.
2272        assert_eq!(c(2, 3).div(&c(2, 3)).unwrap(), Complex::one());
2273        // 1/i == −i.
2274        assert_eq!(Complex::i().recip().unwrap(), c(0, -1));
2275        // (3+4i)/(1+2i) = (3+4i)(1−2i)/5 = (11−2i)/5.
2276        assert_eq!(c(3, 4).div(&c(1, 2)).unwrap(), cq(11, 5, -2, 5));
2277        // Division/reciprocal of zero is None, never a panic.
2278        assert!(c(1, 1).div(&Complex::zero()).is_none());
2279        assert!(Complex::zero().recip().is_none());
2280    }
2281
2282    #[test]
2283    fn complex_conjugate_and_norm_are_exact() {
2284        assert_eq!(c(3, 4).conjugate(), c(3, -4));
2285        assert_eq!(c(3, 4).norm_sqr(), Rational::from_i64(25));
2286        // |3+4i| = 5 exactly (a Pythagorean magnitude).
2287        assert!((c(3, 4).abs_f64() - 5.0).abs() < 1e-12);
2288        // z · conj(z) == |z|² (a real number).
2289        let z = c(2, -5);
2290        assert_eq!(z.mul(&z.conjugate()), Complex::from_rational(z.norm_sqr()));
2291    }
2292
2293    #[test]
2294    fn complex_pow_handles_the_cycle_and_negatives() {
2295        assert_eq!(Complex::i().pow(2).unwrap(), c(-1, 0));
2296        assert_eq!(Complex::i().pow(3).unwrap(), c(0, -1));
2297        assert_eq!(Complex::i().pow(4).unwrap(), c(1, 0));
2298        assert_eq!(Complex::i().pow(0).unwrap(), Complex::one());
2299        assert_eq!(Complex::i().pow(-1).unwrap(), c(0, -1)); // 1/i = −i
2300        // (1+i)^2 = 2i.
2301        assert_eq!(c(1, 1).pow(2).unwrap(), c(0, 2));
2302        // 0^0 == 1; 0 to a negative power is undefined.
2303        assert_eq!(Complex::zero().pow(0).unwrap(), Complex::one());
2304        assert!(Complex::zero().pow(-1).is_none());
2305    }
2306
2307    #[test]
2308    fn complex_display_round_trips_every_form() {
2309        for (z, want) in [
2310            (c(0, 0), "0"),
2311            (c(3, 0), "3"),
2312            (c(0, 1), "i"),
2313            (c(0, -1), "-i"),
2314            (c(0, 4), "4i"),
2315            (c(0, -4), "-4i"),
2316            (c(3, 4), "3+4i"),
2317            (c(3, -4), "3-4i"),
2318            (c(3, 1), "3+i"),
2319            (c(3, -1), "3-i"),
2320            (c(-2, 5), "-2+5i"),
2321        ] {
2322            assert_eq!(z.to_string(), want, "display");
2323            assert_eq!(Complex::parse(want).unwrap(), z, "parse round-trip of {want}");
2324        }
2325        // Fractional parts round-trip too.
2326        let z = cq(1, 2, -3, 4);
2327        assert_eq!(z.to_string(), "1/2-3/4i");
2328        assert_eq!(Complex::parse("1/2-3/4i").unwrap(), z);
2329        // Garbage is rejected.
2330        assert!(Complex::parse("").is_none());
2331        assert!(Complex::parse("3+xi").is_none());
2332    }
2333
2334    #[test]
2335    fn complex_equality_and_hash_are_structural() {
2336        use std::collections::HashSet;
2337        assert_eq!(c(3, 4), c(3, 4));
2338        assert_ne!(c(3, 4), c(3, -4));
2339        assert_eq!(cq(2, 4, 6, 8), cq(1, 2, 3, 4)); // reduced parts compare equal
2340        let mut set = HashSet::new();
2341        set.insert(cq(2, 4, 6, 8));
2342        assert!(set.contains(&cq(1, 2, 3, 4)));
2343    }
2344
2345    #[test]
2346    fn complex_obeys_the_field_laws_under_fuzz() {
2347        // Property fuzz: random Gaussian rationals satisfy the field axioms EXACTLY,
2348        // plus the conjugate/norm homomorphisms.
2349        let mut rng = Rng(0xC011_9EE5_A11C_E5ED);
2350        let pick = |rng: &mut Rng| -> Rational {
2351            let n = (rng.next() % 41) as i64 - 20;
2352            let d = ((rng.next() % 9) as i64) + 1;
2353            Rational::from_ratio_i64(n, d).unwrap()
2354        };
2355        let pick_c = |rng: &mut Rng| -> Complex { Complex::new(pick(rng), pick(rng)) };
2356        for _ in 0..2000 {
2357            let (x, y, z) = (pick_c(&mut rng), pick_c(&mut rng), pick_c(&mut rng));
2358            // commutativity
2359            assert_eq!(x.add(&y), y.add(&x));
2360            assert_eq!(x.mul(&y), y.mul(&x));
2361            // associativity
2362            assert_eq!(x.add(&y).add(&z), x.add(&y.add(&z)));
2363            assert_eq!(x.mul(&y).mul(&z), x.mul(&y.mul(&z)));
2364            // distributivity
2365            assert_eq!(x.mul(&y.add(&z)), x.mul(&y).add(&x.mul(&z)));
2366            // additive inverse + subtraction agreement
2367            assert!(x.add(&x.negated()).is_zero());
2368            assert_eq!(x.sub(&y), x.add(&y.negated()));
2369            // conjugate is an involution and a ring homomorphism
2370            assert_eq!(x.conjugate().conjugate(), x);
2371            assert_eq!(x.mul(&y).conjugate(), x.conjugate().mul(&y.conjugate()));
2372            // the norm is multiplicative: |xy|² = |x|²·|y|²
2373            assert_eq!(x.mul(&y).norm_sqr(), x.norm_sqr().mul(&y.norm_sqr()));
2374            // multiplicative inverse (when nonzero)
2375            if !x.is_zero() {
2376                assert_eq!(x.mul(&x.recip().unwrap()), Complex::one());
2377                assert_eq!(y.div(&x).unwrap(), y.mul(&x.recip().unwrap()));
2378            }
2379        }
2380    }
2381
2382    // -----------------------------------------------------------------
2383    // Modular — the ring ℤ/nℤ
2384    // -----------------------------------------------------------------
2385
2386    fn m(v: i64, n: i64) -> Modular {
2387        Modular::from_i64(v, n).unwrap_or_else(|| panic!("bad modulus {n}"))
2388    }
2389
2390    #[test]
2391    fn modular_reduces_into_range_on_construction() {
2392        assert_eq!(m(10, 7).value().to_i64(), Some(3));
2393        assert_eq!(m(7, 7).value().to_i64(), Some(0));
2394        // Negatives become non-negative residues (Euclidean): −1 mod 7 = 6.
2395        assert_eq!(m(-1, 7).value().to_i64(), Some(6));
2396        assert_eq!(m(-10, 7).value().to_i64(), Some(4));
2397        // Modulus 1 collapses everything to 0 (the trivial ring).
2398        assert_eq!(m(5, 1).value().to_i64(), Some(0));
2399        // A non-positive modulus is rejected.
2400        assert!(Modular::from_i64(3, 0).is_none());
2401        assert!(Modular::from_i64(3, -7).is_none());
2402    }
2403
2404    #[test]
2405    fn modular_arithmetic_wraps_in_the_ring() {
2406        // Add/sub/mul stay in [0, n) — overflow IS the point (cyclic group).
2407        assert_eq!(m(5, 7).add(&m(4, 7)).unwrap(), m(2, 7)); // 9 ≡ 2
2408        assert_eq!(m(3, 7).sub(&m(5, 7)).unwrap(), m(5, 7)); // −2 ≡ 5
2409        assert_eq!(m(4, 7).mul(&m(5, 7)).unwrap(), m(6, 7)); // 20 ≡ 6
2410        // −x is the additive inverse.
2411        assert_eq!(m(3, 7).negated(), m(4, 7));
2412        assert!(m(3, 7).add(&m(3, 7).negated()).unwrap().is_zero());
2413        // A modulus mismatch is refused (a ring mismatch, like a Word width mismatch).
2414        assert!(m(3, 7).add(&m(3, 5)).is_none());
2415        assert!(m(3, 7).mul(&m(3, 5)).is_none());
2416    }
2417
2418    #[test]
2419    fn modular_exponentiation_is_fast_and_exact() {
2420        assert_eq!(m(3, 5).pow(4), m(1, 5)); // 81 ≡ 1 (mod 5)
2421        assert_eq!(m(2, 7).pow(3), m(1, 7)); // 8 ≡ 1 (mod 7)
2422        assert_eq!(m(5, 13).pow(0), m(1, 13)); // x^0 = 1
2423        // A large exponent that fast-exp makes tractable: 7^100 (mod 13).
2424        let mut acc = m(1, 13);
2425        for _ in 0..100 {
2426            acc = acc.mul(&m(7, 13)).unwrap();
2427        }
2428        assert_eq!(m(7, 13).pow(100), acc);
2429    }
2430
2431    #[test]
2432    fn modular_inverse_and_division() {
2433        // 3⁻¹ ≡ 5 (mod 7) since 3·5 = 15 ≡ 1.
2434        assert_eq!(m(3, 7).inverse().unwrap(), m(5, 7));
2435        assert_eq!(m(3, 7).mul(&m(3, 7).inverse().unwrap()).unwrap(), m(1, 7));
2436        // Division is multiplication by the inverse: 1/3 ≡ 5 (mod 7).
2437        assert_eq!(m(1, 7).div(&m(3, 7)).unwrap(), m(5, 7));
2438        assert_eq!(m(4, 7).div(&m(2, 7)).unwrap(), m(2, 7));
2439        // A non-unit (gcd ≠ 1) has NO inverse: 2 is not invertible mod 4.
2440        assert!(m(2, 4).inverse().is_none());
2441        assert!(m(1, 4).div(&m(2, 4)).is_none());
2442        // Division across different moduli is refused.
2443        assert!(m(1, 7).div(&m(3, 5)).is_none());
2444    }
2445
2446    #[test]
2447    fn modular_generalizes_the_word_wrapping_ring() {
2448        // ℤ/2³²ℤ IS the Word32 ring: add/mul agree with the fixed-width wrapping ops.
2449        let modulus = 1i64 << 32; // 2^32 = 4_294_967_296
2450        let (a, b) = (4_000_000_000u32, 1_000_000_000u32); // a + b overflows u32
2451        let word_add = (crate::Word32(a) + crate::Word32(b)).0 as i64;
2452        let mod_add = m(a as i64, modulus).add(&m(b as i64, modulus)).unwrap();
2453        assert_eq!(mod_add.value().to_i64(), Some(word_add), "ℤ/2³² add == Word32 add");
2454        let word_mul = (crate::Word32(a) * crate::Word32(b)).0 as i64;
2455        let mod_mul = m(a as i64, modulus).mul(&m(b as i64, modulus)).unwrap();
2456        assert_eq!(mod_mul.value().to_i64(), Some(word_mul), "ℤ/2³² mul == Word32 mul");
2457    }
2458
2459    #[test]
2460    fn modular_equality_is_per_ring_and_displays_the_modulus() {
2461        // Same residue in DIFFERENT rings is not equal (the modulus is part of the value).
2462        assert_ne!(m(3, 7), m(3, 5));
2463        assert_eq!(m(10, 7), m(3, 7));
2464        assert_eq!(m(3, 7).to_string(), "3 (mod 7)");
2465        assert_eq!(m(-1, 7).to_string(), "6 (mod 7)");
2466    }
2467
2468    #[test]
2469    fn modular_obeys_fermats_little_theorem() {
2470        // For a prime p and a not divisible by p: a^(p−1) ≡ 1 (mod p).
2471        for &p in &[2i64, 3, 5, 7, 13, 97, 101] {
2472            for a in 1..p.min(40) {
2473                assert_eq!(m(a, p).pow((p - 1) as u64), m(1, p), "{a}^({p}-1) ≡ 1 (mod {p})");
2474            }
2475        }
2476    }
2477
2478    #[test]
2479    fn modular_obeys_the_ring_laws_and_inverse_under_fuzz() {
2480        // Property fuzz: random residues over random moduli satisfy the commutative-ring
2481        // axioms exactly, and every unit times its inverse is 1.
2482        let mut rng = Rng(0x_0DDF_ACE5_ABCD_1234);
2483        for _ in 0..3000 {
2484            let n = ((rng.next() % 50) as i64) + 2; // modulus in [2, 51]
2485            let pick = |rng: &mut Rng| (rng.next() % 1_000_000) as i64 - 500_000;
2486            let (a, b, c) = (m(pick(&mut rng), n), m(pick(&mut rng), n), m(pick(&mut rng), n));
2487            // Commutativity.
2488            assert_eq!(a.add(&b).unwrap(), b.add(&a).unwrap());
2489            assert_eq!(a.mul(&b).unwrap(), b.mul(&a).unwrap());
2490            // Associativity.
2491            assert_eq!(a.add(&b).unwrap().add(&c).unwrap(), a.add(&b.add(&c).unwrap()).unwrap());
2492            assert_eq!(a.mul(&b).unwrap().mul(&c).unwrap(), a.mul(&b.mul(&c).unwrap()).unwrap());
2493            // Distributivity.
2494            assert_eq!(
2495                a.mul(&b.add(&c).unwrap()).unwrap(),
2496                a.mul(&b).unwrap().add(&a.mul(&c).unwrap()).unwrap()
2497            );
2498            // Additive inverse.
2499            assert!(a.add(&a.negated()).unwrap().is_zero());
2500            // Multiplicative inverse for units (gcd(a, n) == 1).
2501            if let Some(inv) = a.inverse() {
2502                assert_eq!(a.mul(&inv).unwrap(), m(1, n), "a·a⁻¹ = 1 in ℤ/{n}ℤ");
2503            }
2504        }
2505    }
2506
2507    // ─── Exact float interop ────────────────────────────────────────────
2508
2509    #[test]
2510    fn exact_rational_from_f64_values() {
2511        assert_eq!(rational_from_f64_exact(0.5).unwrap(), Rational::from_ratio_i64(1, 2).unwrap());
2512        assert_eq!(rational_from_f64_exact(3.0).unwrap(), Rational::from_i64(3));
2513        assert_eq!(rational_from_f64_exact(-2.25).unwrap(), Rational::from_ratio_i64(-9, 4).unwrap());
2514        assert_eq!(rational_from_f64_exact(0.0).unwrap(), Rational::zero());
2515        assert_eq!(rational_from_f64_exact(-0.0).unwrap(), Rational::zero());
2516        assert!(rational_from_f64_exact(f64::NAN).is_none());
2517        assert!(rational_from_f64_exact(f64::INFINITY).is_none());
2518        // 0.1 is NOT 1/10 — it is the nearest double, an exact dyadic rational.
2519        assert_ne!(rational_from_f64_exact(0.1).unwrap(), Rational::from_ratio_i64(1, 10).unwrap());
2520    }
2521
2522    #[test]
2523    fn exact_i64_f64_cmp_at_the_representability_boundary() {
2524        let big = 9_007_199_254_740_993_i64; // 2^53 + 1 — not representable
2525        let f = 9_007_199_254_740_992.0_f64; // 2^53 — what the literal rounds to
2526        // A lossy `as f64` compare would say Equal; the exact one says Greater.
2527        assert_eq!(cmp_i64_f64_exact(big, f), Some(Ordering::Greater));
2528        assert_eq!(cmp_i64_f64_exact(big - 1, f), Some(Ordering::Equal));
2529        assert_eq!(cmp_i64_f64_exact(3, 3.5), Some(Ordering::Less));
2530        assert_eq!(cmp_i64_f64_exact(4, 3.5), Some(Ordering::Greater));
2531        assert_eq!(cmp_i64_f64_exact(1, 1.0), Some(Ordering::Equal));
2532        assert_eq!(cmp_i64_f64_exact(0, f64::NAN), None);
2533        assert_eq!(cmp_i64_f64_exact(i64::MAX, f64::INFINITY), Some(Ordering::Less));
2534        assert_eq!(cmp_i64_f64_exact(i64::MIN, f64::NEG_INFINITY), Some(Ordering::Greater));
2535    }
2536
2537    #[test]
2538    fn unified_numeric_hash_coherence() {
2539        // hash(Int k) == hash(Float k) for every exactly-representable k.
2540        for k in [0i64, 1, -1, 2, 42, -42, 1_000_000_007, -(1i64 << 53), 1i64 << 53] {
2541            assert_eq!(
2542                numeric_hash_i64(k),
2543                numeric_hash_f64(k as f64),
2544                "int/float hash mismatch at {k}"
2545            );
2546            assert_eq!(
2547                numeric_hash_i64(k),
2548                numeric_hash_bigint(&BigInt::from_i64(k)),
2549                "int/bigint hash mismatch at {k}"
2550            );
2551            assert_eq!(
2552                numeric_hash_i64(k),
2553                numeric_hash_rational(&Rational::from_i64(k)),
2554                "int/rational hash mismatch at {k}"
2555            );
2556        }
2557        // Non-integral coherence: 0.5 == 1/2 exactly, so hashes agree.
2558        assert_eq!(
2559            numeric_hash_f64(0.5),
2560            numeric_hash_rational(&Rational::from_ratio_i64(1, 2).unwrap())
2561        );
2562        assert_eq!(
2563            numeric_hash_f64(-2.25),
2564            numeric_hash_rational(&Rational::from_ratio_i64(-9, 4).unwrap())
2565        );
2566        // -0.0 and 0.0 are equal, so they hash equal.
2567        assert_eq!(numeric_hash_f64(-0.0), numeric_hash_f64(0.0));
2568        // And a float hashes as its EXACT value: 0.1's hash equals the hash
2569        // of its exact dyadic rational, not of 1/10.
2570        assert_eq!(
2571            numeric_hash_f64(0.1),
2572            numeric_hash_rational(&rational_from_f64_exact(0.1).unwrap())
2573        );
2574    }
2575
2576    // ---- the one-limb boundary (2^64): every op must cross it exactly ----
2577
2578    #[test]
2579    fn arithmetic_is_exact_across_the_one_limb_boundary() {
2580        let u64max = BigInt::from_u64(u64::MAX);
2581        let two64 = u64max.add(&b(1)); // 2^64
2582        assert_eq!(two64.to_string(), "18446744073709551616");
2583        // Back below the boundary.
2584        assert_eq!(two64.sub(&b(1)), u64max);
2585        // Multiplication crossing: (2^32)² = 2^64; 2^62·4 = 2^64.
2586        let two32 = b(1i64 << 32);
2587        assert_eq!(two32.mul(&two32), two64);
2588        assert_eq!(b(1i64 << 62).mul(&b(4)), two64);
2589        // Division re-crossing: 2^64 / 2 = 2^63 (one limb again).
2590        let (q, r) = two64.div_rem(&b(2)).expect("nonzero divisor");
2591        assert!(r.is_zero());
2592        assert_eq!(q.to_string(), "9223372036854775808");
2593        // Negative mirror.
2594        let neg = two64.negated();
2595        assert_eq!(neg.add(&two64), BigInt::zero());
2596        assert_eq!(neg.to_string(), "-18446744073709551616");
2597    }
2598
2599    #[test]
2600    fn ordering_and_hash_are_canonical_across_the_limb_boundary() {
2601        let two64 = BigInt::from_u64(u64::MAX).add(&b(1));
2602        let big = two64.mul(&two64); // 2^128
2603        assert!(b(1) < two64);
2604        assert!(two64 < big);
2605        assert!(big.negated() < b(-1));
2606        assert!(two64.negated() < two64);
2607        // Equal values reached by different construction routes must be equal
2608        // AND hash equal — the representation is canonical, not path-dependent.
2609        let via_mul = b(1i64 << 32).mul(&b(1i64 << 32));
2610        let via_add = BigInt::from_u64(u64::MAX).add(&b(1));
2611        assert_eq!(via_mul, via_add);
2612        use std::collections::hash_map::DefaultHasher;
2613        use std::hash::{Hash, Hasher};
2614        let h = |v: &BigInt| {
2615            let mut s = DefaultHasher::new();
2616            v.hash(&mut s);
2617            s.finish()
2618        };
2619        assert_eq!(h(&via_mul), h(&via_add));
2620        // A value shrunk back under the boundary equals its native construction.
2621        let shrunk = two64.sub(&b(1));
2622        assert_eq!(shrunk, BigInt::from_u64(u64::MAX));
2623        assert_eq!(h(&shrunk), h(&BigInt::from_u64(u64::MAX)));
2624    }
2625
2626    #[test]
2627    fn le_bytes_round_trip_across_the_limb_boundary() {
2628        for s in [
2629            "0",
2630            "1",
2631            "-1",
2632            "18446744073709551615",
2633            "18446744073709551616",
2634            "-18446744073709551616",
2635            "340282366920938463463374607431768211456",
2636        ] {
2637            let v = BigInt::parse_decimal(s).expect("valid decimal");
2638            let (neg, bytes) = v.to_le_bytes();
2639            assert_eq!(BigInt::from_le_bytes(neg, &bytes), v, "round trip {s}");
2640            assert_eq!(v.to_string(), s, "display {s}");
2641        }
2642    }
2643
2644    #[test]
2645    fn div_rem_identity_holds_for_multi_limb_values() {
2646        // a = q·b + r with |r| < |b| and r carrying the dividend's sign, at
2647        // sizes where single-limb arithmetic cannot apply.
2648        let a = b(1_000_003).pow(7); // ~2^140
2649        for dividend in [a.clone(), a.negated()] {
2650            for divisor in [b(97), b(-97), b(1i64 << 40), a.sub(&b(1))] {
2651                let (q, r) = dividend.div_rem(&divisor).expect("nonzero divisor");
2652                assert_eq!(dividend, q.mul(&divisor).add(&r));
2653                assert!(r.abs() < divisor.abs());
2654                assert!(r.is_zero() || r.is_negative() == dividend.is_negative());
2655            }
2656        }
2657    }
2658}