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