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