uni_core/primitives/
trunc_div.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::compat::format;
4use crate::interpreter::Interpreter;
5use crate::primitives::numeric_promotion::promote_pair;
6use crate::value::{RuntimeError, Value};
7use num_traits::Zero;
8#[cfg(not(feature = "std"))]
9use num_traits::Float;
10
11// RUST CONCEPT: Truncating division with zero checking and type promotion
12// Stack-based truncating division: ( n1 n2 -- quotient )
13// Rounds toward zero (like C/Java/Rust integer division)
14pub fn trunc_div_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
15    let b = interp.pop()?;
16    let a = interp.pop()?;
17
18    // Check for division by zero
19    let is_zero = match &b {
20        Value::Int32(i) => *i == 0,
21        Value::Integer(i) => i.is_zero(),
22        Value::Rational(r) => r.is_zero(),
23        Value::Number(n) => *n == 0.0,
24        _ => false,
25    };
26    if is_zero {
27        return Err(RuntimeError::DivisionByZero);
28    }
29
30    // Promote both values to a common type
31    let (pa, pb) = promote_pair(&a, &b);
32
33    let result = match (&pa, &pb) {
34        (Value::Int32(i1), Value::Int32(i2)) => {
35            // Int32 division in Rust already truncates toward zero
36            Value::Int32(i1 / i2)
37        }
38        (Value::Integer(i1), Value::Integer(i2)) => {
39            // Integer division in Rust already truncates toward zero
40            Value::Integer(i1 / i2)
41        }
42        (Value::Rational(r1), Value::Rational(r2)) => {
43            // For rationals, divide and truncate toward zero
44            let division = r1 / r2;
45            let trunc_val = division.trunc();
46            Value::Rational(trunc_val).demote()
47        }
48        (Value::Number(n1), Value::Number(n2)) => Value::Number(n1 / n2).trunc(),
49        _ => {
50            return Err(RuntimeError::TypeError(format!(
51                "Cannot truncating divide {:?} and {:?}",
52                a, b
53            )))
54        }
55    };
56
57    interp.push(result);
58    Ok(())
59}
60
61// RUST CONCEPT: Extension trait to add trunc method to Value
62trait Truncate {
63    fn trunc(self) -> Self;
64}
65
66impl Truncate for Value {
67    fn trunc(self) -> Self {
68        match self {
69            Value::Number(n) => Value::Number(n.trunc()),
70            other => other,
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::value::Value;
79    use num_bigint::BigInt;
80    use num_rational::BigRational;
81
82    fn setup_interpreter() -> Interpreter {
83        Interpreter::new()
84    }
85
86    #[test]
87    fn test_trunc_div_builtin_floats() {
88        let mut interp = setup_interpreter();
89
90        // Test basic truncating division: 7.0 div 2.0 = 3.0
91        interp.push(Value::Number(7.0));
92        interp.push(Value::Number(2.0));
93        trunc_div_builtin(&mut interp).unwrap();
94
95        let result = interp.pop().unwrap();
96        assert!(matches!(result, Value::Number(n) if n == 3.0));
97
98        // Test exact division: 10.0 div 2.0 = 5.0
99        interp.push(Value::Number(10.0));
100        interp.push(Value::Number(2.0));
101        trunc_div_builtin(&mut interp).unwrap();
102
103        let result = interp.pop().unwrap();
104        assert!(matches!(result, Value::Number(n) if n == 5.0));
105
106        // Test with negative dividend: -7.0 div 2.0 = -3.0 (truncate toward zero)
107        interp.push(Value::Number(-7.0));
108        interp.push(Value::Number(2.0));
109        trunc_div_builtin(&mut interp).unwrap();
110
111        let result = interp.pop().unwrap();
112        assert!(matches!(result, Value::Number(n) if n == -3.0));
113
114        // Test with negative divisor: 7.0 div -2.0 = -3.0
115        interp.push(Value::Number(7.0));
116        interp.push(Value::Number(-2.0));
117        trunc_div_builtin(&mut interp).unwrap();
118
119        let result = interp.pop().unwrap();
120        assert!(matches!(result, Value::Number(n) if n == -3.0));
121
122        // Test with both negative: -7.0 div -2.0 = 3.0
123        interp.push(Value::Number(-7.0));
124        interp.push(Value::Number(-2.0));
125        trunc_div_builtin(&mut interp).unwrap();
126
127        let result = interp.pop().unwrap();
128        assert!(matches!(result, Value::Number(n) if n == 3.0));
129    }
130
131    #[test]
132    fn test_trunc_div_builtin_integers() {
133        let mut interp = setup_interpreter();
134
135        // Test Integer div Integer: 7 div 2 = 3
136        interp.push(Value::Integer(BigInt::from(7)));
137        interp.push(Value::Integer(BigInt::from(2)));
138        trunc_div_builtin(&mut interp).unwrap();
139
140        let result = interp.pop().unwrap();
141        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(3)));
142
143        // Test exact division: 10 div 2 = 5
144        interp.push(Value::Integer(BigInt::from(10)));
145        interp.push(Value::Integer(BigInt::from(2)));
146        trunc_div_builtin(&mut interp).unwrap();
147
148        let result = interp.pop().unwrap();
149        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(5)));
150
151        // Test with negative dividend: -7 div 2 = -3 (truncate toward zero)
152        interp.push(Value::Integer(BigInt::from(-7)));
153        interp.push(Value::Integer(BigInt::from(2)));
154        trunc_div_builtin(&mut interp).unwrap();
155
156        let result = interp.pop().unwrap();
157        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(-3)));
158
159        // Test with negative divisor: 7 div -2 = -3
160        interp.push(Value::Integer(BigInt::from(7)));
161        interp.push(Value::Integer(BigInt::from(-2)));
162        trunc_div_builtin(&mut interp).unwrap();
163
164        let result = interp.pop().unwrap();
165        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(-3)));
166
167        // Test with both negative: -7 div -2 = 3
168        interp.push(Value::Integer(BigInt::from(-7)));
169        interp.push(Value::Integer(BigInt::from(-2)));
170        trunc_div_builtin(&mut interp).unwrap();
171
172        let result = interp.pop().unwrap();
173        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(3)));
174    }
175
176    #[test]
177    fn test_trunc_div_builtin_rationals() {
178        let mut interp = setup_interpreter();
179
180        // Test Rational div Rational: (7/2) div (3/2) = 2
181        interp.push(Value::Rational(BigRational::new(
182            BigInt::from(7),
183            BigInt::from(2),
184        )));
185        interp.push(Value::Rational(BigRational::new(
186            BigInt::from(3),
187            BigInt::from(2),
188        )));
189        trunc_div_builtin(&mut interp).unwrap();
190
191        let result = interp.pop().unwrap();
192        // (7/2) / (3/2) = 7/3 = 2.333..., trunc = 2 (demoted to Int32)
193        assert!(matches!(result, Value::Int32(2)));
194
195        // Test negative: (-7/2) div (3/2) = -2 (truncate toward zero, not floor)
196        interp.push(Value::Rational(BigRational::new(
197            BigInt::from(-7),
198            BigInt::from(2),
199        )));
200        interp.push(Value::Rational(BigRational::new(
201            BigInt::from(3),
202            BigInt::from(2),
203        )));
204        trunc_div_builtin(&mut interp).unwrap();
205
206        let result = interp.pop().unwrap();
207        // (-7/2) / (3/2) = -7/3 = -2.333..., trunc = -2 (not floor which is -3, demoted to Int32)
208        assert!(matches!(result, Value::Int32(-2)));
209    }
210
211    #[test]
212    fn test_trunc_div_builtin_mixed_types() {
213        let mut interp = setup_interpreter();
214
215        // Test Integer div Number: 7 div 2.0 = 3.0
216        interp.push(Value::Integer(BigInt::from(7)));
217        interp.push(Value::Number(2.0));
218        trunc_div_builtin(&mut interp).unwrap();
219
220        let result = interp.pop().unwrap();
221        assert!(matches!(result, Value::Number(n) if n == 3.0));
222
223        // Test Rational div Integer: (7/2) div 2 = 1
224        interp.push(Value::Rational(BigRational::new(
225            BigInt::from(7),
226            BigInt::from(2),
227        )));
228        interp.push(Value::Integer(BigInt::from(2)));
229        trunc_div_builtin(&mut interp).unwrap();
230
231        let result = interp.pop().unwrap();
232        // (7/2) / 2 = 7/4 = 1.75, trunc = 1 (demoted to Int32)
233        assert!(matches!(result, Value::Int32(1)));
234    }
235
236    #[test]
237    fn test_trunc_div_vs_floor_div() {
238        let mut interp = setup_interpreter();
239
240        // Key difference: with negative results
241        // -7 div 2: truncate toward zero = -3
242        // -7 // 2: floor = -4
243
244        // Test truncate: -7 div 2 = -3
245        interp.push(Value::Integer(BigInt::from(-7)));
246        interp.push(Value::Integer(BigInt::from(2)));
247        trunc_div_builtin(&mut interp).unwrap();
248
249        let result = interp.pop().unwrap();
250        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(-3)));
251
252        // Compare with positive: 7 div 2 = 3 (same magnitude)
253        interp.push(Value::Integer(BigInt::from(7)));
254        interp.push(Value::Integer(BigInt::from(2)));
255        trunc_div_builtin(&mut interp).unwrap();
256
257        let result = interp.pop().unwrap();
258        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(3)));
259    }
260
261    #[test]
262    fn test_trunc_div_builtin_division_by_zero() {
263        let mut interp = setup_interpreter();
264
265        // Test division by zero with floats
266        interp.push(Value::Number(5.0));
267        interp.push(Value::Number(0.0));
268        let result = trunc_div_builtin(&mut interp);
269        assert!(matches!(result, Err(RuntimeError::DivisionByZero)));
270
271        // Test division by zero with integers
272        interp.push(Value::Integer(BigInt::from(5)));
273        interp.push(Value::Integer(BigInt::from(0)));
274        let result = trunc_div_builtin(&mut interp);
275        assert!(matches!(result, Err(RuntimeError::DivisionByZero)));
276    }
277
278    #[test]
279    fn test_trunc_div_builtin_stack_underflow() {
280        let mut interp = setup_interpreter();
281
282        // Test with empty stack
283        let result = trunc_div_builtin(&mut interp);
284        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
285
286        // Test with only one element
287        interp.push(Value::Number(5.0));
288        let result = trunc_div_builtin(&mut interp);
289        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
290    }
291
292    #[test]
293    fn test_trunc_div_builtin_type_error() {
294        let mut interp = setup_interpreter();
295
296        // Test with wrong types
297        interp.push(Value::Number(5.0));
298        interp.push(Value::Null);
299        let result = trunc_div_builtin(&mut interp);
300        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
301    }
302
303    #[test]
304    fn test_trunc_div_large_numbers() {
305        let mut interp = setup_interpreter();
306
307        // Test with large integers
308        interp.push(Value::Integer(BigInt::from(1000000)));
309        interp.push(Value::Integer(BigInt::from(3)));
310        trunc_div_builtin(&mut interp).unwrap();
311
312        let result = interp.pop().unwrap();
313        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(333333)));
314
315        // Test with large negative
316        interp.push(Value::Integer(BigInt::from(-1000000)));
317        interp.push(Value::Integer(BigInt::from(3)));
318        trunc_div_builtin(&mut interp).unwrap();
319
320        let result = interp.pop().unwrap();
321        assert!(matches!(result, Value::Integer(ref i) if *i == BigInt::from(-333333)));
322    }
323}