json_eval_rs/rlogic/evaluator/
math_ops.rs

1use super::Evaluator;
2use serde_json::Value;
3use super::super::compiled::CompiledLogic;
4use super::helpers;
5
6impl Evaluator {
7    /// Find min or max in a list
8    pub(super) fn eval_min_max(&self, items: &[CompiledLogic], is_max: bool, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
9        if items.is_empty() {
10            return Ok(Value::Null);
11        }
12        let first_val = self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
13        let mut result = helpers::to_f64(&first_val);
14        for item in &items[1..] {
15            let val = self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
16            let num = helpers::to_f64(&val);
17            if is_max {
18                if num > result { result = num; }
19            } else {
20                if num < result { result = num; }
21            }
22        }
23        Ok(self.f64_to_json(result))
24    }
25
26    /// Apply rounding function with optional decimal places (Excel-compatible)
27    /// round_type: 0=round, 1=roundup, 2=rounddown
28    #[inline]
29    pub(super) fn apply_round(&self, expr: &CompiledLogic, decimals_expr: &Option<Box<CompiledLogic>>, round_type: u8, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
30        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
31        let num = helpers::to_f64(&val);
32        
33        let decimals = if let Some(dec_expr) = decimals_expr {
34            let dec_val = self.evaluate_with_context(dec_expr, user_data, internal_context, depth + 1)?;
35            helpers::to_number(&dec_val) as i32
36        } else {
37            0
38        };
39        
40        let result = if decimals == 0 {
41            // Backward compatible: round to integer
42            match round_type {
43                0 => num.round(),
44                1 => num.ceil(),
45                2 => num.floor(),
46                _ => num
47            }
48        } else if decimals > 0 {
49            // Round to decimal places (Excel ROUND)
50            let multiplier = 10f64.powi(decimals);
51            match round_type {
52                0 => (num * multiplier).round() / multiplier,
53                1 => (num * multiplier).ceil() / multiplier,
54                2 => (num * multiplier).floor() / multiplier,
55                _ => num
56            }
57        } else {
58            // Negative decimals: round to left of decimal (Excel ROUND with negative num_digits)
59            let divider = 10f64.powi(-decimals);
60            match round_type {
61                0 => (num / divider).round() * divider,
62                1 => (num / divider).ceil() * divider,
63                2 => (num / divider).floor() * divider,
64                _ => num
65            }
66        };
67        
68        Ok(self.f64_to_json(result))
69    }
70    
71    /// Excel CEILING function - rounds up to nearest multiple of significance
72    pub(super) fn eval_ceiling(&self, expr: &CompiledLogic, significance_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
73        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
74        let num = helpers::to_f64(&val);
75        
76        let significance = if let Some(sig_expr) = significance_expr {
77            let sig_val = self.evaluate_with_context(sig_expr, user_data, internal_context, depth + 1)?;
78            helpers::to_f64(&sig_val)
79        } else {
80            1.0
81        };
82        
83        if significance == 0.0 {
84            return Ok(Value::Number(serde_json::Number::from(0)));
85        }
86        
87        let result = (num / significance).ceil() * significance;
88        Ok(self.f64_to_json(result))
89    }
90    
91    /// Excel FLOOR function - rounds down to nearest multiple of significance
92    pub(super) fn eval_floor(&self, expr: &CompiledLogic, significance_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
93        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
94        let num = helpers::to_f64(&val);
95        
96        let significance = if let Some(sig_expr) = significance_expr {
97            let sig_val = self.evaluate_with_context(sig_expr, user_data, internal_context, depth + 1)?;
98            helpers::to_f64(&sig_val)
99        } else {
100            1.0
101        };
102        
103        if significance == 0.0 {
104            return Ok(Value::Number(serde_json::Number::from(0)));
105        }
106        
107        let result = (num / significance).floor() * significance;
108        Ok(self.f64_to_json(result))
109    }
110    
111    /// Excel TRUNC function - truncates to specified decimals (no rounding)
112    pub(super) fn eval_trunc(&self, expr: &CompiledLogic, decimals_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
113        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
114        let num = helpers::to_f64(&val);
115        
116        let decimals = if let Some(dec_expr) = decimals_expr {
117            let dec_val = self.evaluate_with_context(dec_expr, user_data, internal_context, depth + 1)?;
118            helpers::to_number(&dec_val) as i32
119        } else {
120            0
121        };
122        
123        let result = if decimals == 0 {
124            num.trunc()
125        } else if decimals > 0 {
126            let multiplier = 10f64.powi(decimals);
127            (num * multiplier).trunc() / multiplier
128        } else {
129            let divider = 10f64.powi(-decimals);
130            (num / divider).trunc() * divider
131        };
132        
133        Ok(self.f64_to_json(result))
134    }
135    
136    /// Excel MROUND function - rounds to nearest multiple
137    pub(super) fn eval_mround(&self, value_expr: &CompiledLogic, multiple_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
138        let val = self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
139        let num = helpers::to_f64(&val);
140        
141        let multiple_val = self.evaluate_with_context(multiple_expr, user_data, internal_context, depth + 1)?;
142        let multiple = helpers::to_f64(&multiple_val);
143        
144        if multiple == 0.0 {
145            return Ok(Value::Number(serde_json::Number::from(0)));
146        }
147        
148        let result = (num / multiple).round() * multiple;
149        Ok(self.f64_to_json(result))
150    }
151
152    /// Evaluate unary math operation on expression
153    #[inline]
154    pub(super) fn eval_unary_math<F>(&self, expr: &CompiledLogic, f: F, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String>
155    where F: FnOnce(f64) -> f64
156    {
157        let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
158        let num = helpers::to_f64(&val);
159        Ok(self.f64_to_json(f(num)))
160    }
161
162    /// Evaluate power operation
163    pub(super) fn eval_pow(&self, base_expr: &CompiledLogic, exp_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
164        self.eval_binary_arith(base_expr, exp_expr, |a, b| Some(a.powf(b)), user_data, internal_context, depth)
165    }
166}