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