uni_core/primitives/
numeric_promotion.rs

1// RUST CONCEPT: Numeric type promotion system
2// This module handles automatic type promotion for arithmetic operations
3// It ensures that operations between different numeric types produce sensible results
4
5use crate::value::Value;
6use num_bigint::BigInt;
7#[cfg(feature = "complex_numbers")]
8use num_complex::Complex64;
9use num_rational::BigRational;
10use num_traits::ToPrimitive;
11
12// RUST CONCEPT: Type promotion hierarchy
13// The promotion hierarchy prioritizes exactness:
14// Integer < Rational (both exact)
15// Number and Complex (both inexact, same level)
16// GaussianInt < Complex (exact complex to inexact complex)
17//
18// Mixing exact and inexact types promotes to inexact
19// Once you introduce a float, precision is lost
20
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
22enum NumericType {
23    Int32,       // i32 - fixed-size signed integer (embedded-friendly)
24    Integer,     // BigInt - exact integers
25    Rational,    // BigRational - exact fractions (still exact)
26    #[cfg(feature = "complex_numbers")]
27    GaussianInt, // Gaussian integers (a+bi where a,b are integers, exact)
28    Number,      // f64 - floating point (inexact, loses precision)
29    #[cfg(feature = "complex_numbers")]
30    Complex,     // Complex64 - complex floating point (inexact)
31}
32
33// RUST CONCEPT: Determine the type of a numeric value
34fn numeric_type(val: &Value) -> Option<NumericType> {
35    match val {
36        Value::Int32(_) => Some(NumericType::Int32),
37        Value::Integer(_) => Some(NumericType::Integer),
38        Value::Rational(_) => Some(NumericType::Rational),
39        Value::Number(_) => Some(NumericType::Number),
40        #[cfg(feature = "complex_numbers")]
41        Value::GaussianInt(_, _) => Some(NumericType::GaussianInt),
42        #[cfg(feature = "complex_numbers")]
43        Value::Complex(_) => Some(NumericType::Complex),
44        _ => None,
45    }
46}
47
48// RUST CONCEPT: Promote a value to a target numeric type
49fn promote_to(val: &Value, target: NumericType) -> Value {
50    match (val, target) {
51        // Already the target type
52        (Value::Int32(_), NumericType::Int32) => val.clone(),
53        (Value::Integer(_), NumericType::Integer) => val.clone(),
54        (Value::Rational(_), NumericType::Rational) => val.clone(),
55        (Value::Number(_), NumericType::Number) => val.clone(),
56        #[cfg(feature = "complex_numbers")]
57        (Value::GaussianInt(_, _), NumericType::GaussianInt) => val.clone(),
58        #[cfg(feature = "complex_numbers")]
59        (Value::Complex(_), NumericType::Complex) => val.clone(),
60
61        // Promote Int32 to higher types
62        (Value::Int32(i), NumericType::Integer) => Value::Integer(BigInt::from(*i)),
63        (Value::Int32(i), NumericType::Rational) => {
64            Value::Rational(BigRational::from(BigInt::from(*i)))
65        }
66        (Value::Int32(i), NumericType::Number) => Value::Number(*i as f64),
67        #[cfg(feature = "complex_numbers")]
68        (Value::Int32(i), NumericType::GaussianInt) => {
69            Value::GaussianInt(BigInt::from(*i), BigInt::from(0))
70        }
71        #[cfg(feature = "complex_numbers")]
72        (Value::Int32(i), NumericType::Complex) => Value::Complex(Complex64::new(*i as f64, 0.0)),
73
74        // Promote Integer to higher types
75        (Value::Integer(i), NumericType::Rational) => {
76            Value::Rational(BigRational::from(i.clone()))
77        }
78        (Value::Integer(i), NumericType::Number) => {
79            Value::Number(i.to_f64().unwrap_or(f64::INFINITY))
80        }
81        #[cfg(feature = "complex_numbers")]
82        (Value::Integer(i), NumericType::GaussianInt) => {
83            Value::GaussianInt(i.clone(), BigInt::from(0))
84        }
85        #[cfg(feature = "complex_numbers")]
86        (Value::Integer(i), NumericType::Complex) => {
87            let n = i.to_f64().unwrap_or(f64::INFINITY);
88            Value::Complex(Complex64::new(n, 0.0))
89        }
90
91        // Promote Rational to higher types
92        (Value::Rational(r), NumericType::Number) => {
93            let numer = r.numer().to_f64().unwrap_or(0.0);
94            let denom = r.denom().to_f64().unwrap_or(1.0);
95            Value::Number(numer / denom)
96        }
97        #[cfg(feature = "complex_numbers")]
98        (Value::Rational(r), NumericType::Complex) => {
99            let numer = r.numer().to_f64().unwrap_or(0.0);
100            let denom = r.denom().to_f64().unwrap_or(1.0);
101            Value::Complex(Complex64::new(numer / denom, 0.0))
102        }
103
104        // Promote Number to Complex
105        #[cfg(feature = "complex_numbers")]
106        (Value::Number(n), NumericType::Complex) => Value::Complex(Complex64::new(*n, 0.0)),
107
108        // Promote GaussianInt to Complex
109        #[cfg(feature = "complex_numbers")]
110        (Value::GaussianInt(re, im), NumericType::Complex) => {
111            let re_f = re.to_f64().unwrap_or(f64::INFINITY);
112            let im_f = im.to_f64().unwrap_or(f64::INFINITY);
113            Value::Complex(Complex64::new(re_f, im_f))
114        }
115
116        // Invalid promotions (can't demote or cross between incompatible types)
117        _ => val.clone(), // Fallback: return unchanged
118    }
119}
120
121// RUST CONCEPT: Promote two values to a common type for arithmetic
122// Returns (promoted_a, promoted_b)
123pub fn promote_pair(a: &Value, b: &Value) -> (Value, Value) {
124    let type_a = numeric_type(a);
125    let type_b = numeric_type(b);
126
127    match (type_a, type_b) {
128        (Some(ta), Some(tb)) => {
129            // Determine the target type (the "higher" one in the hierarchy)
130            let target = if ta >= tb { ta.clone() } else { tb.clone() };
131
132            // Handle special case: GaussianInt + Number should go to Complex
133            #[cfg(feature = "complex_numbers")]
134            let target = match (&ta, &tb) {
135                (NumericType::GaussianInt, NumericType::Number)
136                | (NumericType::Number, NumericType::GaussianInt) => NumericType::Complex,
137                (NumericType::GaussianInt, NumericType::Rational)
138                | (NumericType::Rational, NumericType::GaussianInt) => NumericType::Complex,
139                _ => target,
140            };
141
142            #[cfg(not(feature = "complex_numbers"))]
143            let target = target;
144
145            (promote_to(a, target.clone()), promote_to(b, target))
146        }
147        // If either value is not numeric, return unchanged
148        _ => (a.clone(), b.clone()),
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_promote_same_types() {
158        let a = Value::Integer(BigInt::from(5));
159        let b = Value::Integer(BigInt::from(3));
160        let (pa, pb) = promote_pair(&a, &b);
161        assert!(matches!(pa, Value::Integer(_)));
162        assert!(matches!(pb, Value::Integer(_)));
163    }
164
165    #[test]
166    fn test_promote_integer_to_rational() {
167        let a = Value::Integer(BigInt::from(5));
168        let b = Value::Rational(BigRational::from(BigInt::from(3)));
169        let (pa, pb) = promote_pair(&a, &b);
170        assert!(matches!(pa, Value::Rational(_)));
171        assert!(matches!(pb, Value::Rational(_)));
172    }
173
174    #[test]
175    fn test_promote_integer_to_number() {
176        let a = Value::Integer(BigInt::from(5));
177        let b = Value::Number(3.14);
178        let (pa, pb) = promote_pair(&a, &b);
179        assert!(matches!(pa, Value::Number(_)));
180        assert!(matches!(pb, Value::Number(_)));
181    }
182
183    #[test]
184    #[cfg(feature = "complex_numbers")]
185    fn test_promote_number_to_complex() {
186        let a = Value::Number(5.0);
187        let b = Value::Complex(Complex64::new(3.0, 4.0));
188        let (pa, pb) = promote_pair(&a, &b);
189        assert!(matches!(pa, Value::Complex(_)));
190        assert!(matches!(pb, Value::Complex(_)));
191    }
192
193    #[test]
194    #[cfg(feature = "complex_numbers")]
195    fn test_promote_gaussian_to_complex() {
196        let a = Value::GaussianInt(BigInt::from(5), BigInt::from(2));
197        let b = Value::Number(3.14);
198        let (pa, pb) = promote_pair(&a, &b);
199        assert!(matches!(pa, Value::Complex(_)));
200        assert!(matches!(pb, Value::Complex(_)));
201    }
202}