Skip to main content

prima_core/
number.rs

1use crate::error::CoreError;
2use std::fmt;
3
4use num_bigint::BigInt;
5use num_rational::BigRational;
6use num_traits::{One, Signed, ToPrimitive, Zero};
7
8/// Inexact real (spec §6.1). `NaN`/`Inf` are allowed to exist only in this layer (spec §6.2),
9/// and only arise from explicit collapse; they never enter the symbolic layer.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum Real {
12    F32(f32),
13    F64(f64),
14}
15
16impl std::hash::Hash for Real {
17    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
18        match self {
19            Real::F32(f) => f.to_bits().hash(state),
20            Real::F64(f) => f.to_bits().hash(state),
21        }
22    }
23}
24
25/// Numeric tower (spec §6.1): the exact layer `Integer`/`Rational`/`Complex`, the inexact layer `Real`,
26/// and the fixed-width collapsed layer (`I8`…`U128`/`Isize`/`Usize`/`BigFloat`) that maps 1:1 to Rust
27/// primitives. Collapsed types exist **only after explicit collapse** and do not participate in implicit
28/// promotion; they are normalized to the exact/`Real` layer before any arithmetic (spec §6.1).
29/// The exact layer stays exact by default; a `Real` infects the result to inexact (spec §6.4 promotion rules).
30#[derive(Debug, Clone, PartialEq)]
31pub enum Number {
32    Integer(BigInt),
33    Rational(BigRational),
34    Real(Real),
35    Complex { re: Box<Number>, im: Box<Number> },
36    // —— fixed-width collapsed layer (spec §6.1, maps 1:1 to Rust primitives) ——
37    I8(i8), I16(i16), I32(i32), I64(i64), I128(i128),
38    U8(u8), U16(u16), U32(u32), U64(u64), U128(u128),
39    Isize(isize), Usize(usize),
40    BigFloat(f64),
41}
42
43impl Number {
44    pub fn complex(re: i64, im: i64) -> Number {
45        Number::Complex {
46            re: Box::new(Number::Integer(BigInt::from(re))),
47            im: Box::new(Number::Integer(BigInt::from(im))),
48        }
49    }
50
51    pub fn is_complex(&self) -> bool {
52        matches!(self, Number::Complex { .. })
53    }
54
55    pub fn is_zero(&self) -> bool {
56        match self {
57            Number::Integer(i) => i.is_zero(),
58            Number::Rational(r) => r.is_zero(),
59            Number::Real(Real::F32(f)) => *f == 0.0,
60            Number::Real(Real::F64(f)) => *f == 0.0,
61            Number::Complex { re, im } => re.is_zero() && im.is_zero(),
62            other => normalize(other.clone()).is_zero(),
63        }
64    }
65
66    pub fn is_one(&self) -> bool {
67        match self {
68            Number::Integer(i) => i == &BigInt::from(1),
69            Number::Rational(r) => r == &BigRational::new(BigInt::from(1), BigInt::from(1)),
70            Number::Real(Real::F32(f)) => *f == 1.0,
71            Number::Real(Real::F64(f)) => *f == 1.0,
72            Number::Complex { .. } => false,
73            other => normalize(other.clone()).is_one(),
74        }
75    }
76
77    pub fn abs(&self) -> Number {
78        match self {
79            Number::Integer(i) => Number::Integer(i.abs()),
80            Number::Rational(r) => Number::Rational(r.abs()),
81            Number::Real(Real::F32(x)) => Number::Real(Real::F32(x.abs())),
82            Number::Real(Real::F64(x)) => Number::Real(Real::F64(x.abs())),
83            Number::Complex { .. } => self.clone(),
84            other => normalize(other.clone()).abs(),
85        }
86    }
87
88    pub fn sqrt(&self) -> Option<Number> {
89        match self {
90            Number::Integer(n) => isqrt(n).map(Number::Integer),
91            Number::Rational(r) => {
92                let p = isqrt(r.numer())?;
93                let q = isqrt(r.denom())?;
94                Some(Number::Rational(BigRational::new(p, q)))
95            }
96            Number::Real(Real::F32(x)) => Some(Number::Real(Real::F32(x.sqrt()))),
97            Number::Real(Real::F64(x)) => Some(Number::Real(Real::F64(x.sqrt()))),
98            Number::Complex { .. } => None,
99            other => normalize(other.clone()).sqrt(),
100        }
101    }
102
103    pub fn pow(&self, exp: &Number) -> Option<Number> {
104        let base = normalize(self.clone());
105        let exp = normalize(exp.clone());
106        match (&base, &exp) {
107            (Number::Integer(a), Number::Integer(b)) => {
108                if b.is_zero() {
109                    return Some(Number::Integer(BigInt::one()));
110                }
111                let neg = *b < BigInt::zero();
112                let mag = if neg { -b } else { b.clone() };
113                let e = mag.to_u32()?;
114                if neg && a.is_zero() {
115                    return None;
116                }
117                let p = a.pow(e);
118                if neg {
119                    Some(normalized(BigInt::one(), p))
120                } else {
121                    Some(Number::Integer(p))
122                }
123            }
124            (Number::Rational(a), Number::Integer(b)) => {
125                if b.is_zero() {
126                    return Some(Number::Integer(BigInt::one()));
127                }
128                let neg = *b < BigInt::zero();
129                let mag = if neg { -b } else { b.clone() };
130                let e = mag.to_u32()?;
131                if neg && a.is_zero() {
132                    return None;
133                }
134                let p = a.numer().pow(e);
135                let q = a.denom().pow(e);
136                if neg {
137                    Some(normalized(q, p))
138                } else {
139                    Some(normalized(p, q))
140                }
141            }
142            (Number::Real(x), Number::Integer(b)) => {
143                let n = b.to_i32()?;
144                match x {
145                    Real::F32(f) => Some(Number::Real(Real::F32(f.powi(n)))),
146                    Real::F64(f) => Some(Number::Real(Real::F64(f.powi(n)))),
147                }
148            }
149            (Number::Real(x), Number::Rational(r)) => {
150                let v = r.to_f64()?;
151                match x {
152                    Real::F32(f) => Some(Number::Real(Real::F32(f.powf(v as f32)))),
153                    Real::F64(f) => Some(Number::Real(Real::F64(f.powf(v)))),
154                }
155            }
156            (Number::Integer(a), Number::Rational(r)) => {
157                if *r.denom() == BigInt::one() {
158                    return base.pow(&Number::Integer(r.numer().clone()));
159                }
160                // Exact x^(1/2): return an exact square root for perfect (rational) squares, otherwise leave it to the symbolic layer (spec §7.4: `sqrt(-1)→\i` depends on the domain).
161                if *r.denom() == BigInt::from(2) && *r.numer() == BigInt::one() {
162                    return base.sqrt();
163                }
164                let _ = a;
165                None
166            }
167            (Number::Rational(a), Number::Rational(r)) => {
168                if *r.denom() == BigInt::one() {
169                    return base.pow(&Number::Integer(r.numer().clone()));
170                }
171                if *r.denom() == BigInt::from(2) && *r.numer() == BigInt::one() {
172                    return base.sqrt();
173                }
174                let _ = a;
175                None
176            }
177            _ => None,
178        }
179    }
180
181    /// Numeric conversion (spec §9.2 `to_f64`): both the exact layer and `Real` convert; complex returns `NaN` (callers must check `is_complex` first).
182    pub fn to_f64_lossy(&self) -> f64 {
183        match self {
184            Number::Integer(i) => i.to_f64().unwrap_or(f64::NAN),
185            Number::Rational(r) => r.to_f64().unwrap_or(f64::NAN),
186            Number::Real(Real::F32(f)) => *f as f64,
187            Number::Real(Real::F64(f)) => *f,
188            Number::Complex { .. } => f64::NAN,
189            Number::I8(v) => *v as f64,
190            Number::I16(v) => *v as f64,
191            Number::I32(v) => *v as f64,
192            Number::I64(v) => *v as f64,
193            Number::I128(v) => *v as f64,
194            Number::U8(v) => *v as f64,
195            Number::U16(v) => *v as f64,
196            Number::U32(v) => *v as f64,
197            Number::U64(v) => *v as f64,
198            Number::U128(v) => *v as f64,
199            Number::Isize(v) => *v as f64,
200            Number::Usize(v) => *v as f64,
201            Number::BigFloat(f) => *f,
202        }
203    }
204
205    /// Exact conversion to `i64` (only integral values that do not overflow), otherwise `None`.
206    pub fn as_i64(&self) -> Option<i64> {
207        match self {
208            Number::Integer(i) => i.to_i64(),
209            Number::Rational(r) if *r.denom() == BigInt::one() => r.numer().to_i64(),
210            Number::Real(Real::F64(f)) if f.fract() == 0.0 && (*f as i64) as f64 == *f => Some(*f as i64),
211            Number::Real(Real::F32(f)) if f.fract() == 0.0 && (*f as i64) as f64 == *f as f64 => Some(*f as i64),
212            Number::I8(v) => Some(*v as i64),
213            Number::I16(v) => Some(*v as i64),
214            Number::I32(v) => Some(*v as i64),
215            Number::I64(v) => Some(*v),
216            Number::I128(v) => i64::try_from(*v).ok(),
217            Number::U8(v) => Some(*v as i64),
218            Number::U16(v) => Some(*v as i64),
219            Number::U32(v) => Some(*v as i64),
220            Number::U64(v) => i64::try_from(*v).ok(),
221            Number::U128(v) => i64::try_from(*v).ok(),
222            Number::Isize(v) => Some(*v as i64),
223            Number::Usize(v) => i64::try_from(*v).ok(),
224            Number::BigFloat(f) if f.fract() == 0.0 && (*f as i64) as f64 == *f => Some(*f as i64),
225            _ => None,
226        }
227    }
228
229    /// Exact conversion to `i32` (only integral values that do not overflow), otherwise `None`.
230    pub fn as_i32(&self) -> Option<i32> {
231        self.as_i64().and_then(|v| i32::try_from(v).ok())
232    }
233
234    /// Exact conversion to `u64` (only non-negative integral values that do not overflow), otherwise `None`.
235    pub fn as_u64(&self) -> Option<u64> {
236        match self {
237            Number::Integer(i) => i.to_u64(),
238            Number::Rational(r) if *r.denom() == BigInt::one() => r.numer().to_u64(),
239            Number::Real(Real::F64(f)) if f.fract() == 0.0 && f.is_sign_positive() && (*f as u64) as f64 == *f => {
240                Some(*f as u64)
241            }
242            Number::Real(Real::F32(f)) if f.fract() == 0.0 && f.is_sign_positive() && (*f as u64) as f64 == *f as f64 => {
243                Some(*f as u64)
244            }
245            Number::I8(v) if *v >= 0 => Some(*v as u64),
246            Number::I16(v) if *v >= 0 => Some(*v as u64),
247            Number::I32(v) if *v >= 0 => Some(*v as u64),
248            Number::I64(v) if *v >= 0 => Some(*v as u64),
249            Number::I128(v) => u64::try_from(*v).ok(),
250            Number::U8(v) => Some(*v as u64),
251            Number::U16(v) => Some(*v as u64),
252            Number::U32(v) => Some(*v as u64),
253            Number::U64(v) => Some(*v),
254            Number::U128(v) => u64::try_from(*v).ok(),
255            Number::Isize(v) if *v >= 0 => Some(*v as u64),
256            Number::Usize(v) => u64::try_from(*v).ok(),
257            Number::BigFloat(f) if f.fract() == 0.0 && f.is_sign_positive() && (*f as u64) as f64 == *f => {
258                Some(*f as u64)
259            }
260            _ => None,
261        }
262    }
263
264    /// Conversion to `BigInt` (only integral values, spec §9.2 `to_bigint`).
265    pub fn as_bigint(&self) -> Option<BigInt> {
266        match self {
267            Number::Integer(i) => Some(i.clone()),
268            Number::Rational(r) if *r.denom() == BigInt::one() => Some(r.numer().clone()),
269            Number::Real(Real::F64(f)) if f.fract() == 0.0 => Some(BigInt::from(*f as i64)),
270            Number::Real(Real::F32(f)) if f.fract() == 0.0 => Some(BigInt::from(*f as i64)),
271            Number::I8(v) => Some(BigInt::from(*v)),
272            Number::I16(v) => Some(BigInt::from(*v)),
273            Number::I32(v) => Some(BigInt::from(*v)),
274            Number::I64(v) => Some(BigInt::from(*v)),
275            Number::I128(v) => Some(BigInt::from(*v)),
276            Number::U8(v) => Some(BigInt::from(*v)),
277            Number::U16(v) => Some(BigInt::from(*v)),
278            Number::U32(v) => Some(BigInt::from(*v)),
279            Number::U64(v) => Some(BigInt::from(*v)),
280            Number::U128(v) => Some(BigInt::from(*v)),
281            Number::Isize(v) => Some(BigInt::from(*v)),
282            Number::Usize(v) => Some(BigInt::from(*v)),
283            Number::BigFloat(f) if f.fract() == 0.0 => Some(BigInt::from(*f as i64)),
284            _ => None,
285        }
286    }
287
288    /// Conversion to `BigRational` (exact layer, spec §9.2 `to_rational`).
289    pub fn as_rational(&self) -> Option<BigRational> {
290        match self {
291            Number::Integer(i) => Some(BigRational::from_integer(i.clone())),
292            Number::Rational(r) => Some(r.clone()),
293            Number::Real(Real::F64(f)) if f.fract() == 0.0 => Some(BigRational::from_integer(BigInt::from(*f as i64))),
294            Number::Real(Real::F32(f)) if f.fract() == 0.0 => Some(BigRational::from_integer(BigInt::from(*f as i64))),
295            Number::I8(v) => Some(BigRational::from_integer(BigInt::from(*v))),
296            Number::I16(v) => Some(BigRational::from_integer(BigInt::from(*v))),
297            Number::I32(v) => Some(BigRational::from_integer(BigInt::from(*v))),
298            Number::I64(v) => Some(BigRational::from_integer(BigInt::from(*v))),
299            Number::I128(v) => Some(BigRational::from_integer(BigInt::from(*v))),
300            Number::U8(v) => Some(BigRational::from_integer(BigInt::from(*v))),
301            Number::U16(v) => Some(BigRational::from_integer(BigInt::from(*v))),
302            Number::U32(v) => Some(BigRational::from_integer(BigInt::from(*v))),
303            Number::U64(v) => Some(BigRational::from_integer(BigInt::from(*v))),
304            Number::U128(v) => Some(BigRational::from_integer(BigInt::from(*v))),
305            Number::Isize(v) => Some(BigRational::from_integer(BigInt::from(*v))),
306            Number::Usize(v) => Some(BigRational::from_integer(BigInt::from(*v))),
307            Number::BigFloat(f) if f.fract() == 0.0 => Some(BigRational::from_integer(BigInt::from(*f as i64))),
308            _ => None,
309        }
310    }
311
312    /// Whether this is an integral value (no fractional part, prerequisite for integer collapse in spec §9.2).
313    pub fn is_integer_value(&self) -> bool {
314        self.as_bigint().is_some()
315    }
316
317    /// Range-checked conversion to `i8` (spec §6.1 collapse layer): exact/fixed-width integral values
318    /// convert if representable; `Real`/`BigFloat` convert only when integral and in range; complex never converts.
319    pub fn as_i8(&self) -> Option<i8> {
320        exact_integer(self).and_then(|b| b.to_i8())
321    }
322
323    /// Range-checked conversion to `i16` (spec §6.1 collapse layer); see `as_i8`.
324    pub fn as_i16(&self) -> Option<i16> {
325        exact_integer(self).and_then(|b| b.to_i16())
326    }
327
328    /// Range-checked conversion to `i128` (spec §6.1 collapse layer); see `as_i8`.
329    pub fn as_i128(&self) -> Option<i128> {
330        exact_integer(self).and_then(|b| b.to_i128())
331    }
332
333    /// Range-checked conversion to `u8` (spec §6.1 collapse layer); see `as_i8`.
334    pub fn as_u8(&self) -> Option<u8> {
335        exact_integer(self).and_then(|b| b.to_u8())
336    }
337
338    /// Range-checked conversion to `u16` (spec §6.1 collapse layer); see `as_i8`.
339    pub fn as_u16(&self) -> Option<u16> {
340        exact_integer(self).and_then(|b| b.to_u16())
341    }
342
343    /// Range-checked conversion to `u32` (spec §6.1 collapse layer); see `as_i8`.
344    pub fn as_u32(&self) -> Option<u32> {
345        exact_integer(self).and_then(|b| b.to_u32())
346    }
347
348    /// Range-checked conversion to `u128` (spec §6.1 collapse layer); see `as_i8`.
349    pub fn as_u128(&self) -> Option<u128> {
350        exact_integer(self).and_then(|b| b.to_u128())
351    }
352
353    /// Range-checked conversion to `isize` (spec §6.1 collapse layer); see `as_i8`.
354    pub fn as_isize(&self) -> Option<isize> {
355        exact_integer(self).and_then(|b| b.to_isize())
356    }
357
358    /// Range-checked conversion to `usize` (spec §6.1 collapse layer); see `as_i8`.
359    pub fn as_usize(&self) -> Option<usize> {
360        exact_integer(self).and_then(|b| b.to_usize())
361    }
362
363    /// Lossy conversion to `f32` (like `to_f64_lossy`); complex values never convert (`None`).
364    pub fn as_f32(&self) -> Option<f32> {
365        match self {
366            Number::Complex { .. } => None,
367            Number::Real(Real::F32(f)) => Some(*f),
368            _ => Some(self.to_f64_lossy() as f32),
369        }
370    }
371
372    /// Truncate toward zero to an integer (spec §9.6 `truncated_i32`).
373    pub fn truncate(&self) -> Number {
374        match self {
375            Number::Integer(_) => self.clone(),
376            Number::Rational(r) => {
377                let t = r.to_integer();
378                normalized(t, BigInt::one())
379            }
380            Number::Real(Real::F64(f)) => Number::Real(Real::F64(f.trunc())),
381            Number::Real(Real::F32(f)) => Number::Real(Real::F32(f.trunc())),
382            Number::Complex { .. } => self.clone(),
383            other => normalize(other.clone()).truncate(),
384        }
385    }
386
387    /// Round to the nearest integer (spec §9.6 `rounded_i32`).
388    pub fn round(&self) -> Number {
389        match self {
390            Number::Integer(_) => self.clone(),
391            Number::Rational(r) => normalized(r.round().numer().clone(), BigInt::one()),
392            Number::Real(Real::F64(f)) => Number::Real(Real::F64(f.round())),
393            Number::Real(Real::F32(f)) => Number::Real(Real::F32(f.round())),
394            Number::Complex { .. } => self.clone(),
395            other => normalize(other.clone()).round(),
396        }
397    }
398
399    /// Round to a fixed number of decimal digits (spec §9.6 `rounded_f64(x, digits)`).
400    pub fn rounded_digits(&self, digits: i64) -> Number {
401        let mult = 10f64.powi(digits as i32);
402        let v = (self.to_f64_lossy() * mult).round() / mult;
403        Number::Real(Real::F64(v))
404    }
405
406    /// Clamp to `[min, max]` (spec §9.5 `clamped_f64`).
407    pub fn clamped_f64(&self, min: f64, max: f64) -> Number {
408        let v = self.to_f64_lossy();
409        Number::Real(Real::F64(v.clamp(min, max)))
410    }
411}
412
413// Integer square root via Newton iteration: returns `None` for non-perfect squares so exact `sqrt` stays symbolic.
414fn isqrt(n: &BigInt) -> Option<BigInt> {
415    if n < &BigInt::zero() {
416        return None;
417    }
418    if n.is_zero() {
419        return Some(BigInt::zero());
420    }
421    let bits = n.bits();
422    let mut x = BigInt::one() << bits.div_ceil(2);
423    loop {
424        let y = (&x + n / &x) >> 1;
425        if y >= x {
426            break;
427        }
428        x = y;
429    }
430    if &x * &x == *n {
431        Some(x)
432    } else {
433        None
434    }
435}
436
437impl From<i32> for Number {
438    fn from(v: i32) -> Number {
439        Number::Integer(BigInt::from(v))
440    }
441}
442
443impl From<i64> for Number {
444    fn from(v: i64) -> Number {
445        Number::Integer(BigInt::from(v))
446    }
447}
448
449impl From<f64> for Number {
450    fn from(v: f64) -> Number {
451        Number::Real(Real::F64(v))
452    }
453}
454
455fn to_rational(n: &Number) -> Number {
456    match n {
457        Number::Integer(i) => Number::Rational(BigRational::new(i.clone(), BigInt::one())),
458        Number::Rational(_) => n.clone(),
459        _ => unreachable!("to_rational called on non-rational"),
460    }
461}
462
463fn normalized(numer: BigInt, denom: BigInt) -> Number {
464    if denom == BigInt::one() {
465        Number::Integer(numer)
466    } else {
467        Number::Rational(BigRational::new(numer, denom))
468    }
469}
470
471fn to_f64(n: &Number) -> Number {
472    match n {
473        Number::Integer(i) => Number::Real(Real::F64(i.to_f64().unwrap_or(f64::NAN))),
474        Number::Rational(r) => Number::Real(Real::F64(r.to_f64().unwrap_or(f64::NAN))),
475        Number::Real(Real::F32(f)) => Number::Real(Real::F64(*f as f64)),
476        Number::Real(Real::F64(f)) => Number::Real(Real::F64(*f)),
477        _ => unreachable!("to_f64 called on complex"),
478    }
479}
480
481fn to_real(n: &Number, like: &Real) -> Number {
482    let v = match n {
483        Number::Integer(i) => i.to_f64().unwrap_or(f64::NAN),
484        Number::Rational(r) => r.to_f64().unwrap_or(f64::NAN),
485        Number::Real(Real::F32(f)) => *f as f64,
486        Number::Real(Real::F64(f)) => *f,
487        _ => unreachable!("to_real called on complex"),
488    };
489    match like {
490        Real::F32(_) => Number::Real(Real::F32(v as f32)),
491        Real::F64(_) => Number::Real(Real::F64(v)),
492    }
493}
494
495fn convert_to(n: &Number, like: &Number) -> Number {
496    match like {
497        Number::Rational(_) => to_rational(n),
498        Number::Real(Real::F64(_)) => to_f64(n),
499        Number::Real(Real::F32(_)) => to_real(n, &Real::F32(0.0)),
500        _ => n.clone(),
501    }
502}
503
504fn zero_like(like: &Number) -> Number {
505    match like {
506        Number::Integer(_) => Number::Integer(BigInt::zero()),
507        Number::Rational(_) => Number::Rational(BigRational::new(BigInt::zero(), BigInt::one())),
508        Number::Real(Real::F32(_)) => Number::Real(Real::F32(0.0)),
509        Number::Real(Real::F64(_)) => Number::Real(Real::F64(0.0)),
510        Number::Complex { re, im } => Number::Complex { re: Box::new(zero_like(re)), im: Box::new(zero_like(im)) },
511        // Fixed-width collapsed variants normalize to the zero of the exact/`Real` layer (spec §6.1).
512        other => zero_like(&normalize(other.clone())),
513    }
514}
515
516/// Normalize a fixed-width collapsed value to the exact/inexact layer (spec §6.1): fixed-width
517/// integers become `Integer`, `BigFloat` becomes `Real(F64)`; everything else is identity.
518/// Collapsed types exist only after explicit collapse and never meet the promotion code raw.
519fn normalize(n: Number) -> Number {
520    match n {
521        Number::I8(v) => Number::Integer(BigInt::from(v)),
522        Number::I16(v) => Number::Integer(BigInt::from(v)),
523        Number::I32(v) => Number::Integer(BigInt::from(v)),
524        Number::I64(v) => Number::Integer(BigInt::from(v)),
525        Number::I128(v) => Number::Integer(BigInt::from(v)),
526        Number::U8(v) => Number::Integer(BigInt::from(v)),
527        Number::U16(v) => Number::Integer(BigInt::from(v)),
528        Number::U32(v) => Number::Integer(BigInt::from(v)),
529        Number::U64(v) => Number::Integer(BigInt::from(v)),
530        Number::U128(v) => Number::Integer(BigInt::from(v)),
531        Number::Isize(v) => Number::Integer(BigInt::from(v)),
532        Number::Usize(v) => Number::Integer(BigInt::from(v)),
533        Number::BigFloat(f) => Number::Real(Real::F64(f)),
534        other => other,
535    }
536}
537
538/// Exact integral value as `BigInt`, guarded like `as_i64`/`as_u64` (only integral values that do not
539/// overflow i64), else `None`. Backs the range-checked collapse conversions (spec §6.1/§9.2).
540fn exact_integer(n: &Number) -> Option<BigInt> {
541    match n {
542        Number::Integer(i) => Some(i.clone()),
543        Number::Rational(r) if *r.denom() == BigInt::one() => Some(r.numer().clone()),
544        Number::Real(Real::F64(f)) if f.fract() == 0.0 && (*f as i64) as f64 == *f => Some(BigInt::from(*f as i64)),
545        Number::Real(Real::F32(f)) if f.fract() == 0.0 && (*f as i64) as f64 == *f as f64 => Some(BigInt::from(*f as i64)),
546        Number::I8(v) => Some(BigInt::from(*v)),
547        Number::I16(v) => Some(BigInt::from(*v)),
548        Number::I32(v) => Some(BigInt::from(*v)),
549        Number::I64(v) => Some(BigInt::from(*v)),
550        Number::I128(v) => Some(BigInt::from(*v)),
551        Number::U8(v) => Some(BigInt::from(*v)),
552        Number::U16(v) => Some(BigInt::from(*v)),
553        Number::U32(v) => Some(BigInt::from(*v)),
554        Number::U64(v) => Some(BigInt::from(*v)),
555        Number::U128(v) => Some(BigInt::from(*v)),
556        Number::Isize(v) => Some(BigInt::from(*v)),
557        Number::Usize(v) => Some(BigInt::from(*v)),
558        Number::BigFloat(f) if f.fract() == 0.0 && (*f as i64) as f64 == *f => Some(BigInt::from(*f as i64)),
559        _ => None,
560    }
561}
562
563fn promote_real(a: &Number, b: &Number) -> (Number, Number) {
564    let a = normalize(a.clone());
565    let b = normalize(b.clone());
566    match (&a, &b) {
567        (Number::Integer(_), Number::Integer(_)) => (a.clone(), b.clone()),
568        (Number::Rational(_), Number::Rational(_)) => (a.clone(), b.clone()),
569        (Number::Integer(_), Number::Rational(_)) | (Number::Rational(_), Number::Integer(_)) => {
570            (to_rational(&a), to_rational(&b))
571        }
572        (Number::Real(Real::F32(_)), Number::Real(Real::F32(_))) => (a.clone(), b.clone()),
573        (Number::Real(Real::F64(_)), Number::Real(Real::F64(_))) => (a.clone(), b.clone()),
574        (Number::Real(Real::F64(_)), Number::Real(Real::F32(_))) | (Number::Real(Real::F32(_)), Number::Real(Real::F64(_))) => {
575            (to_f64(&a), to_f64(&b))
576        }
577        (Number::Real(x), Number::Integer(_)) | (Number::Real(x), Number::Rational(_)) => {
578            (a.clone(), to_real(&b, x))
579        }
580        (Number::Integer(_), Number::Real(x)) | (Number::Rational(_), Number::Real(x)) => {
581            (to_real(&a, x), b.clone())
582        }
583        (Number::Complex { .. }, _) | (_, Number::Complex { .. }) => unreachable!("complex promoted by caller"),
584        // Fixed-width variants are normalized before promotion (spec §6.1); never reached.
585        _ => unreachable!("fixed-width variants must be normalized before promote_real"),
586    }
587}
588
589/// Promote two numbers to a common type (spec §6.4).
590/// Promotion sequence: `Integer < Rational < Complex<Rational> < F64 < Complex<F64>`;
591/// a `Real` infects, promoting the whole `Complex` to `Complex<Real>`.
592/// Fixed-width collapsed variants are normalized to the exact/`Real` layer first (spec §6.1).
593pub fn promote(a: &Number, b: &Number) -> (Number, Number) {
594    let a = normalize(a.clone());
595    let b = normalize(b.clone());
596    use Number::*;
597    let a_complex = matches!(&a, Complex { .. });
598    let b_complex = matches!(&b, Complex { .. });
599    match (a_complex, b_complex) {
600        (false, false) => promote_real(&a, &b),
601        (true, true) => {
602            let (Complex { re: rea, im: ima }, Complex { re: reb, im: imb }) = (a, b) else {
603                unreachable!()
604            };
605            let (nrea, nreb) = promote_real(&rea, &reb);
606            let (nima, nimb) = promote_real(&ima, &imb);
607            (
608                Complex { re: Box::new(nrea), im: Box::new(nima) },
609                Complex { re: Box::new(nreb), im: Box::new(nimb) },
610            )
611        }
612        (true, false) => {
613            let Complex { re, im } = a else { unreachable!() };
614            let (nre, nb) = promote_real(&re, &b);
615            let nima = convert_to(&im, &nre);
616            let nb_c = Complex { re: Box::new(nb), im: Box::new(zero_like(&nima)) };
617            (Complex { re: Box::new(nre), im: Box::new(nima) }, nb_c)
618        }
619        (false, true) => {
620            let Complex { re, im } = b else { unreachable!() };
621            let (na, nre) = promote_real(&a, &re);
622            let nima = convert_to(&im, &nre);
623            let na_c = Complex { re: Box::new(na), im: Box::new(zero_like(&nima)) };
624            (na_c, Complex { re: Box::new(nre), im: Box::new(nima) })
625        }
626    }
627}
628
629fn add_real(a: Real, b: Real) -> Real {
630    match (a, b) {
631        (Real::F32(x), Real::F32(y)) => Real::F32(x + y),
632        _ => {
633            let x = match a {
634                Real::F32(f) => f as f64,
635                Real::F64(f) => f,
636            };
637            let y = match b {
638                Real::F32(f) => f as f64,
639                Real::F64(f) => f,
640            };
641            Real::F64(x + y)
642        }
643    }
644}
645
646fn mul_real(a: Real, b: Real) -> Real {
647    match (a, b) {
648        (Real::F32(x), Real::F32(y)) => Real::F32(x * y),
649        _ => {
650            let x = match a {
651                Real::F32(f) => f as f64,
652                Real::F64(f) => f,
653            };
654            let y = match b {
655                Real::F32(f) => f as f64,
656                Real::F64(f) => f,
657            };
658            Real::F64(x * y)
659        }
660    }
661}
662
663fn div_real(a: Real, b: Real) -> Real {
664    match (a, b) {
665        (Real::F32(x), Real::F32(y)) => Real::F32(x / y),
666        _ => {
667            let x = match a {
668                Real::F32(f) => f as f64,
669                Real::F64(f) => f,
670            };
671            let y = match b {
672                Real::F32(f) => f as f64,
673                Real::F64(f) => f,
674            };
675            Real::F64(x / y)
676        }
677    }
678}
679
680fn checked_denominator(n: &Number) -> Result<(), CoreError> {
681    if n.is_zero() {
682        Err(CoreError::DivisionByZero)
683    } else {
684        Ok(())
685    }
686}
687
688fn complex_div(a: Number, b: Number, c: Number, d: Number) -> Number {
689    let c2 = c.clone() * c.clone();
690    let d2 = d.clone() * d.clone();
691    let denom = c2 + d2;
692    checked_denominator(&denom).expect("division by zero");
693    let re = (a.clone() * c.clone() + b.clone() * d.clone()) / denom.clone();
694    let im = (b * c - a * d) / denom;
695    Number::Complex { re: Box::new(re), im: Box::new(im) }
696}
697
698impl std::ops::Add for Number {
699    type Output = Number;
700    fn add(self, rhs: Number) -> Number {
701        let (a, b) = promote(&normalize(self), &normalize(rhs));
702        use Number::*;
703        match (a, b) {
704            (Integer(x), Integer(y)) => Integer(x + y),
705            (Rational(x), Rational(y)) => { let r = x + y; normalized(r.numer().clone(), r.denom().clone()) },
706            (Real(x), Real(y)) => Real(add_real(x, y)),
707            (Complex { re, im }, Complex { re: u, im: v }) => Complex { re: Box::new(*re + *u), im: Box::new(*im + *v) },
708            _ => unreachable!("promote must align operands"),
709        }
710    }
711}
712
713impl std::ops::Sub for Number {
714    type Output = Number;
715    fn sub(self, rhs: Number) -> Number {
716        let (a, b) = promote(&normalize(self), &normalize(rhs));
717        match (a, b) {
718            (Number::Integer(x), Number::Integer(y)) => Number::Integer(x - y),
719            (Number::Rational(x), Number::Rational(y)) => {
720                let r = x - y;
721                normalized(r.numer().clone(), r.denom().clone())
722            }
723            (Number::Real(rx), Number::Real(ry)) => match (rx, ry) {
724                (Real::F32(x), Real::F32(y)) => Number::Real(Real::F32(x - y)),
725                _ => {
726                    let x = match rx {
727                        Real::F32(f) => f as f64,
728                        Real::F64(f) => f,
729                    };
730                    let y = match ry {
731                        Real::F32(f) => f as f64,
732                        Real::F64(f) => f,
733                    };
734                    Number::Real(Real::F64(x - y))
735                }
736            },
737            (Number::Complex { re, im }, Number::Complex { re: u, im: v }) => {
738                Number::Complex { re: Box::new(*re - *u), im: Box::new(*im - *v) }
739            }
740            _ => unreachable!("promote must align operands"),
741        }
742    }
743}
744
745impl std::ops::Mul for Number {
746    type Output = Number;
747    fn mul(self, rhs: Number) -> Number {
748        let (a, b) = promote(&normalize(self), &normalize(rhs));
749        use Number::*;
750        match (a, b) {
751            (Integer(x), Integer(y)) => Integer(x * y),
752            (Rational(x), Rational(y)) => { let r = x * y; normalized(r.numer().clone(), r.denom().clone()) },
753            (Real(x), Real(y)) => Real(mul_real(x, y)),
754            (Complex { re, im }, Complex { re: u, im: v }) => {
755                let re_new = *re.clone() * *u.clone() - *im.clone() * *v.clone();
756                let im_new = *re * *v + *im * *u;
757                Complex { re: Box::new(re_new), im: Box::new(im_new) }
758            }
759            _ => unreachable!("promote must align operands"),
760        }
761    }
762}
763
764impl std::ops::Div for Number {
765    type Output = Number;
766    fn div(self, rhs: Number) -> Number {
767        let (a, b) = promote(&normalize(self), &normalize(rhs));
768        use Number::*;
769        match (a, b) {
770            (Integer(x), Integer(y)) => {
771                if y.is_zero() {
772                    panic!("division by zero");
773                }
774                normalized(x, y)
775            }
776            (Rational(x), Rational(y)) => {
777                if y.is_zero() {
778                    panic!("division by zero");
779                }
780                let r = x / y;
781                normalized(r.numer().clone(), r.denom().clone())
782            }
783            (Real(x), Real(y)) => Real(div_real(x, y)),
784            (Complex { re, im }, Complex { re: u, im: v }) => complex_div(*re, *im, *u, *v),
785            _ => unreachable!("promote must align operands"),
786        }
787    }
788}
789
790impl std::ops::Neg for Number {
791    type Output = Number;
792    fn neg(self) -> Number {
793        match normalize(self) {
794            Number::Integer(i) => Number::Integer(-i),
795            Number::Rational(r) => Number::Rational(-r),
796            Number::Real(Real::F32(f)) => Number::Real(Real::F32(-f)),
797            Number::Real(Real::F64(f)) => Number::Real(Real::F64(-f)),
798            Number::Complex { re, im } => Number::Complex { re: Box::new(-*re), im: Box::new(-*im) },
799            _ => unreachable!("normalize returns only the exact/Real/complex layer"),
800        }
801    }
802}
803
804impl fmt::Display for Real {
805    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806        match self {
807            Real::F32(v) => write!(f, "{v}"),
808            Real::F64(v) => write!(f, "{v}"),
809        }
810    }
811}
812
813impl fmt::Display for Number {
814    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815        match self {
816            Number::Integer(i) => write!(f, "{i}"),
817            Number::Rational(r) => write!(f, "{}/{}", r.numer(), r.denom()),
818            Number::Real(r) => write!(f, "{r}"),
819            Number::Complex { re, im } => write!(f, "{re} + {im}i"),
820            Number::I8(v) => write!(f, "{v}"),
821            Number::I16(v) => write!(f, "{v}"),
822            Number::I32(v) => write!(f, "{v}"),
823            Number::I64(v) => write!(f, "{v}"),
824            Number::I128(v) => write!(f, "{v}"),
825            Number::U8(v) => write!(f, "{v}"),
826            Number::U16(v) => write!(f, "{v}"),
827            Number::U32(v) => write!(f, "{v}"),
828            Number::U64(v) => write!(f, "{v}"),
829            Number::U128(v) => write!(f, "{v}"),
830            Number::Isize(v) => write!(f, "{v}"),
831            Number::Usize(v) => write!(f, "{v}"),
832            Number::BigFloat(x) => write!(f, "{x}"),
833        }
834    }
835}