formualizer_eval/builtins/math/
numeric.rs

1use super::super::utils::{ARG_NUM_LENIENT_ONE, ARG_NUM_LENIENT_TWO, coerce_num};
2use crate::args::ArgSchema;
3use crate::function::Function;
4use crate::traits::{ArgumentHandle, FunctionContext};
5use formualizer_common::{ExcelError, LiteralValue};
6use formualizer_macros::func_caps;
7
8#[derive(Debug)]
9pub struct AbsFn;
10impl Function for AbsFn {
11    func_caps!(PURE);
12    fn name(&self) -> &'static str {
13        "ABS"
14    }
15    fn min_args(&self) -> usize {
16        1
17    }
18    fn arg_schema(&self) -> &'static [ArgSchema] {
19        &ARG_NUM_LENIENT_ONE[..]
20    }
21    fn eval_scalar<'a, 'b>(
22        &self,
23        args: &'a [ArgumentHandle<'a, 'b>],
24        _: &dyn FunctionContext,
25    ) -> Result<LiteralValue, ExcelError> {
26        let v = args[0].value()?;
27        match v.as_ref() {
28            LiteralValue::Error(e) => Ok(LiteralValue::Error(e.clone())),
29            other => Ok(LiteralValue::Number(coerce_num(other)?.abs())),
30        }
31    }
32}
33
34#[derive(Debug)]
35pub struct SignFn;
36impl Function for SignFn {
37    func_caps!(PURE);
38    fn name(&self) -> &'static str {
39        "SIGN"
40    }
41    fn min_args(&self) -> usize {
42        1
43    }
44    fn arg_schema(&self) -> &'static [ArgSchema] {
45        &ARG_NUM_LENIENT_ONE[..]
46    }
47    fn eval_scalar<'a, 'b>(
48        &self,
49        args: &'a [ArgumentHandle<'a, 'b>],
50        _: &dyn FunctionContext,
51    ) -> Result<LiteralValue, ExcelError> {
52        let v = args[0].value()?;
53        match v.as_ref() {
54            LiteralValue::Error(e) => Ok(LiteralValue::Error(e.clone())),
55            other => {
56                let n = coerce_num(other)?;
57                Ok(LiteralValue::Number(if n > 0.0 {
58                    1.0
59                } else if n < 0.0 {
60                    -1.0
61                } else {
62                    0.0
63                }))
64            }
65        }
66    }
67}
68
69#[derive(Debug)]
70pub struct IntFn; // floor toward -inf
71impl Function for IntFn {
72    func_caps!(PURE);
73    fn name(&self) -> &'static str {
74        "INT"
75    }
76    fn min_args(&self) -> usize {
77        1
78    }
79    fn arg_schema(&self) -> &'static [ArgSchema] {
80        &ARG_NUM_LENIENT_ONE[..]
81    }
82    fn eval_scalar<'a, 'b>(
83        &self,
84        args: &'a [ArgumentHandle<'a, 'b>],
85        _: &dyn FunctionContext,
86    ) -> Result<LiteralValue, ExcelError> {
87        let v = args[0].value()?;
88        match v.as_ref() {
89            LiteralValue::Error(e) => Ok(LiteralValue::Error(e.clone())),
90            other => Ok(LiteralValue::Number(coerce_num(other)?.floor())),
91        }
92    }
93}
94
95#[derive(Debug)]
96pub struct TruncFn; // truncate toward zero
97impl Function for TruncFn {
98    func_caps!(PURE);
99    fn name(&self) -> &'static str {
100        "TRUNC"
101    }
102    fn min_args(&self) -> usize {
103        1
104    }
105    fn variadic(&self) -> bool {
106        true
107    }
108    fn arg_schema(&self) -> &'static [ArgSchema] {
109        &ARG_NUM_LENIENT_TWO[..]
110    }
111    fn eval_scalar<'a, 'b>(
112        &self,
113        args: &'a [ArgumentHandle<'a, 'b>],
114        _: &dyn FunctionContext,
115    ) -> Result<LiteralValue, ExcelError> {
116        if args.is_empty() || args.len() > 2 {
117            return Ok(LiteralValue::Error(ExcelError::new_value()));
118        }
119        let mut n = match args[0].value()?.as_ref() {
120            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
121            other => coerce_num(other)?,
122        };
123        let digits: i32 = if args.len() == 2 {
124            match args[1].value()?.as_ref() {
125                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
126                other => coerce_num(other)? as i32,
127            }
128        } else {
129            0
130        };
131        if digits >= 0 {
132            let f = 10f64.powi(digits);
133            n = (n * f).trunc() / f;
134        } else {
135            let f = 10f64.powi(-digits);
136            n = (n / f).trunc() * f;
137        }
138        Ok(LiteralValue::Number(n))
139    }
140}
141
142#[derive(Debug)]
143pub struct RoundFn; // ROUND(number, digits)
144impl Function for RoundFn {
145    func_caps!(PURE);
146    fn name(&self) -> &'static str {
147        "ROUND"
148    }
149    fn min_args(&self) -> usize {
150        2
151    }
152    fn arg_schema(&self) -> &'static [ArgSchema] {
153        &ARG_NUM_LENIENT_TWO[..]
154    }
155    fn eval_scalar<'a, 'b>(
156        &self,
157        args: &'a [ArgumentHandle<'a, 'b>],
158        _: &dyn FunctionContext,
159    ) -> Result<LiteralValue, ExcelError> {
160        let n = match args[0].value()?.as_ref() {
161            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
162            other => coerce_num(other)?,
163        };
164        let digits = match args[1].value()?.as_ref() {
165            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
166            other => coerce_num(other)? as i32,
167        };
168        let f = 10f64.powi(digits.abs());
169        let out = if digits >= 0 {
170            (n * f).round() / f
171        } else {
172            (n / f).round() * f
173        };
174        Ok(LiteralValue::Number(out))
175    }
176}
177
178#[derive(Debug)]
179pub struct RoundDownFn; // toward zero
180impl Function for RoundDownFn {
181    func_caps!(PURE);
182    fn name(&self) -> &'static str {
183        "ROUNDDOWN"
184    }
185    fn min_args(&self) -> usize {
186        2
187    }
188    fn arg_schema(&self) -> &'static [ArgSchema] {
189        &ARG_NUM_LENIENT_TWO[..]
190    }
191    fn eval_scalar<'a, 'b>(
192        &self,
193        args: &'a [ArgumentHandle<'a, 'b>],
194        _: &dyn FunctionContext,
195    ) -> Result<LiteralValue, ExcelError> {
196        let n = match args[0].value()?.as_ref() {
197            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
198            other => coerce_num(other)?,
199        };
200        let digits = match args[1].value()?.as_ref() {
201            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
202            other => coerce_num(other)? as i32,
203        };
204        let f = 10f64.powi(digits.abs());
205        let out = if digits >= 0 {
206            (n * f).trunc() / f
207        } else {
208            (n / f).trunc() * f
209        };
210        Ok(LiteralValue::Number(out))
211    }
212}
213
214#[derive(Debug)]
215pub struct RoundUpFn; // away from zero
216impl Function for RoundUpFn {
217    func_caps!(PURE);
218    fn name(&self) -> &'static str {
219        "ROUNDUP"
220    }
221    fn min_args(&self) -> usize {
222        2
223    }
224    fn arg_schema(&self) -> &'static [ArgSchema] {
225        &ARG_NUM_LENIENT_TWO[..]
226    }
227    fn eval_scalar<'a, 'b>(
228        &self,
229        args: &'a [ArgumentHandle<'a, 'b>],
230        _: &dyn FunctionContext,
231    ) -> Result<LiteralValue, ExcelError> {
232        let n = match args[0].value()?.as_ref() {
233            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
234            other => coerce_num(other)?,
235        };
236        let digits = match args[1].value()?.as_ref() {
237            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
238            other => coerce_num(other)? as i32,
239        };
240        let f = 10f64.powi(digits.abs());
241        let mut scaled = if digits >= 0 { n * f } else { n / f };
242        if scaled > 0.0 {
243            scaled = scaled.ceil();
244        } else {
245            scaled = scaled.floor();
246        }
247        let out = if digits >= 0 { scaled / f } else { scaled * f };
248        Ok(LiteralValue::Number(out))
249    }
250}
251
252#[derive(Debug)]
253pub struct ModFn; // MOD(a,b)
254impl Function for ModFn {
255    func_caps!(PURE);
256    fn name(&self) -> &'static str {
257        "MOD"
258    }
259    fn min_args(&self) -> usize {
260        2
261    }
262    fn arg_schema(&self) -> &'static [ArgSchema] {
263        &ARG_NUM_LENIENT_TWO[..]
264    }
265    fn eval_scalar<'a, 'b>(
266        &self,
267        args: &'a [ArgumentHandle<'a, 'b>],
268        _: &dyn FunctionContext,
269    ) -> Result<LiteralValue, ExcelError> {
270        let x = match args[0].value()?.as_ref() {
271            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
272            other => coerce_num(other)?,
273        };
274        let y = match args[1].value()?.as_ref() {
275            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
276            other => coerce_num(other)?,
277        };
278        if y == 0.0 {
279            return Ok(LiteralValue::Error(ExcelError::from_error_string(
280                "#DIV/0!",
281            )));
282        }
283        let m = x % y;
284        let mut r = if m == 0.0 {
285            0.0
286        } else if (y > 0.0 && m < 0.0) || (y < 0.0 && m > 0.0) {
287            m + y
288        } else {
289            m
290        };
291        if r == -0.0 {
292            r = 0.0;
293        }
294        Ok(LiteralValue::Number(r))
295    }
296}
297
298/* ───────────────────── Additional Math / Rounding ───────────────────── */
299
300#[derive(Debug)]
301pub struct CeilingFn; // CEILING(number, [significance]) legacy semantics simplified
302impl Function for CeilingFn {
303    func_caps!(PURE);
304    fn name(&self) -> &'static str {
305        "CEILING"
306    }
307    fn min_args(&self) -> usize {
308        1
309    }
310    fn variadic(&self) -> bool {
311        true
312    }
313    fn arg_schema(&self) -> &'static [ArgSchema] {
314        &ARG_NUM_LENIENT_TWO[..]
315    }
316    fn eval_scalar<'a, 'b>(
317        &self,
318        args: &'a [ArgumentHandle<'a, 'b>],
319        _: &dyn FunctionContext,
320    ) -> Result<LiteralValue, ExcelError> {
321        if args.is_empty() || args.len() > 2 {
322            return Ok(LiteralValue::Error(ExcelError::new_value()));
323        }
324        let n = match args[0].value()?.as_ref() {
325            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
326            other => coerce_num(other)?,
327        };
328        let mut sig = if args.len() == 2 {
329            match args[1].value()?.as_ref() {
330                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
331                other => coerce_num(other)?,
332            }
333        } else {
334            1.0
335        };
336        if sig == 0.0 {
337            return Ok(LiteralValue::Error(ExcelError::from_error_string(
338                "#DIV/0!",
339            )));
340        }
341        if sig < 0.0 {
342            sig = sig.abs(); /* Excel nuances: #NUM! when sign mismatch; simplified TODO */
343        }
344        let k = (n / sig).ceil();
345        Ok(LiteralValue::Number(k * sig))
346    }
347}
348
349#[derive(Debug)]
350pub struct CeilingMathFn; // CEILING.MATH(number,[significance],[mode])
351impl Function for CeilingMathFn {
352    func_caps!(PURE);
353    fn name(&self) -> &'static str {
354        "CEILING.MATH"
355    }
356    fn min_args(&self) -> usize {
357        1
358    }
359    fn variadic(&self) -> bool {
360        true
361    }
362    fn arg_schema(&self) -> &'static [ArgSchema] {
363        &ARG_NUM_LENIENT_TWO[..]
364    } // allow up to 3 handled manually
365    fn eval_scalar<'a, 'b>(
366        &self,
367        args: &'a [ArgumentHandle<'a, 'b>],
368        _: &dyn FunctionContext,
369    ) -> Result<LiteralValue, ExcelError> {
370        if args.is_empty() || args.len() > 3 {
371            return Ok(LiteralValue::Error(ExcelError::new_value()));
372        }
373        let n = match args[0].value()?.as_ref() {
374            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
375            other => coerce_num(other)?,
376        };
377        let sig = if args.len() >= 2 {
378            match args[1].value()?.as_ref() {
379                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
380                other => {
381                    let v = coerce_num(other)?;
382                    if v == 0.0 { 1.0 } else { v.abs() }
383                }
384            }
385        } else {
386            1.0
387        }; // significance default 1, use abs
388        let mode_nonzero = if args.len() == 3 {
389            match args[2].value()?.as_ref() {
390                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
391                other => coerce_num(other)? != 0.0,
392            }
393        } else {
394            false
395        };
396        let result = if n >= 0.0 {
397            (n / sig).ceil() * sig
398        } else if mode_nonzero {
399            (n / sig).floor() * sig /* away from zero */
400        } else {
401            (n / sig).ceil() * sig /* toward +inf (less negative) */
402        };
403        Ok(LiteralValue::Number(result))
404    }
405}
406
407#[derive(Debug)]
408pub struct FloorFn; // FLOOR(number,[significance])
409impl Function for FloorFn {
410    func_caps!(PURE);
411    fn name(&self) -> &'static str {
412        "FLOOR"
413    }
414    fn min_args(&self) -> usize {
415        1
416    }
417    fn variadic(&self) -> bool {
418        true
419    }
420    fn arg_schema(&self) -> &'static [ArgSchema] {
421        &ARG_NUM_LENIENT_TWO[..]
422    }
423    fn eval_scalar<'a, 'b>(
424        &self,
425        args: &'a [ArgumentHandle<'a, 'b>],
426        _: &dyn FunctionContext,
427    ) -> Result<LiteralValue, ExcelError> {
428        if args.is_empty() || args.len() > 2 {
429            return Ok(LiteralValue::Error(ExcelError::new_value()));
430        }
431        let n = match args[0].value()?.as_ref() {
432            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
433            other => coerce_num(other)?,
434        };
435        let mut sig = if args.len() == 2 {
436            match args[1].value()?.as_ref() {
437                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
438                other => coerce_num(other)?,
439            }
440        } else {
441            1.0
442        };
443        if sig == 0.0 {
444            return Ok(LiteralValue::Error(ExcelError::from_error_string(
445                "#DIV/0!",
446            )));
447        }
448        if sig < 0.0 {
449            sig = sig.abs();
450        }
451        let k = (n / sig).floor();
452        Ok(LiteralValue::Number(k * sig))
453    }
454}
455
456#[derive(Debug)]
457pub struct FloorMathFn; // FLOOR.MATH(number,[significance],[mode])
458impl Function for FloorMathFn {
459    func_caps!(PURE);
460    fn name(&self) -> &'static str {
461        "FLOOR.MATH"
462    }
463    fn min_args(&self) -> usize {
464        1
465    }
466    fn variadic(&self) -> bool {
467        true
468    }
469    fn arg_schema(&self) -> &'static [ArgSchema] {
470        &ARG_NUM_LENIENT_TWO[..]
471    }
472    fn eval_scalar<'a, 'b>(
473        &self,
474        args: &'a [ArgumentHandle<'a, 'b>],
475        _: &dyn FunctionContext,
476    ) -> Result<LiteralValue, ExcelError> {
477        if args.is_empty() || args.len() > 3 {
478            return Ok(LiteralValue::Error(ExcelError::new_value()));
479        }
480        let n = match args[0].value()?.as_ref() {
481            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
482            other => coerce_num(other)?,
483        };
484        let sig = if args.len() >= 2 {
485            match args[1].value()?.as_ref() {
486                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
487                other => {
488                    let v = coerce_num(other)?;
489                    if v == 0.0 { 1.0 } else { v.abs() }
490                }
491            }
492        } else {
493            1.0
494        };
495        let mode_nonzero = if args.len() == 3 {
496            match args[2].value()?.as_ref() {
497                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
498                other => coerce_num(other)? != 0.0,
499            }
500        } else {
501            false
502        };
503        let result = if n >= 0.0 {
504            (n / sig).floor() * sig
505        } else if mode_nonzero {
506            (n / sig).ceil() * sig
507        } else {
508            (n / sig).floor() * sig
509        };
510        Ok(LiteralValue::Number(result))
511    }
512}
513
514#[derive(Debug)]
515pub struct SqrtFn; // SQRT(number)
516impl Function for SqrtFn {
517    func_caps!(PURE);
518    fn name(&self) -> &'static str {
519        "SQRT"
520    }
521    fn min_args(&self) -> usize {
522        1
523    }
524    fn arg_schema(&self) -> &'static [ArgSchema] {
525        &ARG_NUM_LENIENT_ONE[..]
526    }
527    fn eval_scalar<'a, 'b>(
528        &self,
529        args: &'a [ArgumentHandle<'a, 'b>],
530        _: &dyn FunctionContext,
531    ) -> Result<LiteralValue, ExcelError> {
532        let n = match args[0].value()?.as_ref() {
533            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
534            other => coerce_num(other)?,
535        };
536        if n < 0.0 {
537            return Ok(LiteralValue::Error(ExcelError::new_num()));
538        }
539        Ok(LiteralValue::Number(n.sqrt()))
540    }
541}
542
543#[derive(Debug)]
544pub struct PowerFn; // POWER(number, power)
545impl Function for PowerFn {
546    func_caps!(PURE);
547    fn name(&self) -> &'static str {
548        "POWER"
549    }
550    fn min_args(&self) -> usize {
551        2
552    }
553    fn arg_schema(&self) -> &'static [ArgSchema] {
554        &ARG_NUM_LENIENT_TWO[..]
555    }
556    fn eval_scalar<'a, 'b>(
557        &self,
558        args: &'a [ArgumentHandle<'a, 'b>],
559        _: &dyn FunctionContext,
560    ) -> Result<LiteralValue, ExcelError> {
561        let base = match args[0].value()?.as_ref() {
562            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
563            other => coerce_num(other)?,
564        };
565        let expv = match args[1].value()?.as_ref() {
566            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
567            other => coerce_num(other)?,
568        };
569        if base < 0.0 && (expv.fract().abs() > 1e-12) {
570            return Ok(LiteralValue::Error(ExcelError::new_num()));
571        }
572        Ok(LiteralValue::Number(base.powf(expv)))
573    }
574}
575
576#[derive(Debug)]
577pub struct ExpFn; // EXP(number)
578impl Function for ExpFn {
579    func_caps!(PURE);
580    fn name(&self) -> &'static str {
581        "EXP"
582    }
583    fn min_args(&self) -> usize {
584        1
585    }
586    fn arg_schema(&self) -> &'static [ArgSchema] {
587        &ARG_NUM_LENIENT_ONE[..]
588    }
589    fn eval_scalar<'a, 'b>(
590        &self,
591        args: &'a [ArgumentHandle<'a, 'b>],
592        _: &dyn FunctionContext,
593    ) -> Result<LiteralValue, ExcelError> {
594        let n = match args[0].value()?.as_ref() {
595            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
596            other => coerce_num(other)?,
597        };
598        Ok(LiteralValue::Number(n.exp()))
599    }
600}
601
602#[derive(Debug)]
603pub struct LnFn; // LN(number)
604impl Function for LnFn {
605    func_caps!(PURE);
606    fn name(&self) -> &'static str {
607        "LN"
608    }
609    fn min_args(&self) -> usize {
610        1
611    }
612    fn arg_schema(&self) -> &'static [ArgSchema] {
613        &ARG_NUM_LENIENT_ONE[..]
614    }
615    fn eval_scalar<'a, 'b>(
616        &self,
617        args: &'a [ArgumentHandle<'a, 'b>],
618        _: &dyn FunctionContext,
619    ) -> Result<LiteralValue, ExcelError> {
620        let n = match args[0].value()?.as_ref() {
621            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
622            other => coerce_num(other)?,
623        };
624        if n <= 0.0 {
625            return Ok(LiteralValue::Error(ExcelError::new_num()));
626        }
627        Ok(LiteralValue::Number(n.ln()))
628    }
629}
630
631#[derive(Debug)]
632pub struct LogFn; // LOG(number,[base]) default base 10
633impl Function for LogFn {
634    func_caps!(PURE);
635    fn name(&self) -> &'static str {
636        "LOG"
637    }
638    fn min_args(&self) -> usize {
639        1
640    }
641    fn variadic(&self) -> bool {
642        true
643    }
644    fn arg_schema(&self) -> &'static [ArgSchema] {
645        &ARG_NUM_LENIENT_TWO[..]
646    }
647    fn eval_scalar<'a, 'b>(
648        &self,
649        args: &'a [ArgumentHandle<'a, 'b>],
650        _: &dyn FunctionContext,
651    ) -> Result<LiteralValue, ExcelError> {
652        if args.is_empty() || args.len() > 2 {
653            return Ok(LiteralValue::Error(ExcelError::new_value()));
654        }
655        let n = match args[0].value()?.as_ref() {
656            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
657            other => coerce_num(other)?,
658        };
659        let base = if args.len() == 2 {
660            match args[1].value()?.as_ref() {
661                LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
662                other => coerce_num(other)?,
663            }
664        } else {
665            10.0
666        };
667        if n <= 0.0 || base <= 0.0 || (base - 1.0).abs() < 1e-12 {
668            return Ok(LiteralValue::Error(ExcelError::new_num()));
669        }
670        Ok(LiteralValue::Number(n.log(base)))
671    }
672}
673
674#[derive(Debug)]
675pub struct Log10Fn; // LOG10(number)
676impl Function for Log10Fn {
677    func_caps!(PURE);
678    fn name(&self) -> &'static str {
679        "LOG10"
680    }
681    fn min_args(&self) -> usize {
682        1
683    }
684    fn arg_schema(&self) -> &'static [ArgSchema] {
685        &ARG_NUM_LENIENT_ONE[..]
686    }
687    fn eval_scalar<'a, 'b>(
688        &self,
689        args: &'a [ArgumentHandle<'a, 'b>],
690        _: &dyn FunctionContext,
691    ) -> Result<LiteralValue, ExcelError> {
692        let n = match args[0].value()?.as_ref() {
693            LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
694            other => coerce_num(other)?,
695        };
696        if n <= 0.0 {
697            return Ok(LiteralValue::Error(ExcelError::new_num()));
698        }
699        Ok(LiteralValue::Number(n.log10()))
700    }
701}
702
703pub fn register_builtins() {
704    use std::sync::Arc;
705    crate::function_registry::register_function(Arc::new(AbsFn));
706    crate::function_registry::register_function(Arc::new(SignFn));
707    crate::function_registry::register_function(Arc::new(IntFn));
708    crate::function_registry::register_function(Arc::new(TruncFn));
709    crate::function_registry::register_function(Arc::new(RoundFn));
710    crate::function_registry::register_function(Arc::new(RoundDownFn));
711    crate::function_registry::register_function(Arc::new(RoundUpFn));
712    crate::function_registry::register_function(Arc::new(ModFn));
713    crate::function_registry::register_function(Arc::new(CeilingFn));
714    crate::function_registry::register_function(Arc::new(CeilingMathFn));
715    crate::function_registry::register_function(Arc::new(FloorFn));
716    crate::function_registry::register_function(Arc::new(FloorMathFn));
717    crate::function_registry::register_function(Arc::new(SqrtFn));
718    crate::function_registry::register_function(Arc::new(PowerFn));
719    crate::function_registry::register_function(Arc::new(ExpFn));
720    crate::function_registry::register_function(Arc::new(LnFn));
721    crate::function_registry::register_function(Arc::new(LogFn));
722    crate::function_registry::register_function(Arc::new(Log10Fn));
723}
724
725#[cfg(test)]
726mod tests_numeric {
727    use super::*;
728    use crate::test_workbook::TestWorkbook;
729    use crate::traits::ArgumentHandle;
730    use formualizer_common::LiteralValue;
731    use formualizer_parse::parser::{ASTNode, ASTNodeType};
732
733    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
734        wb.interpreter()
735    }
736    fn lit(v: LiteralValue) -> ASTNode {
737        ASTNode::new(ASTNodeType::Literal(v), None)
738    }
739
740    // ABS
741    #[test]
742    fn abs_basic() {
743        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AbsFn));
744        let ctx = interp(&wb);
745        let n = lit(LiteralValue::Number(-5.5));
746        let f = ctx.context.get_function("", "ABS").unwrap();
747        assert_eq!(
748            f.dispatch(
749                &[ArgumentHandle::new(&n, &ctx)],
750                &ctx.function_context(None)
751            )
752            .unwrap(),
753            LiteralValue::Number(5.5)
754        );
755    }
756    #[test]
757    fn abs_error_passthrough() {
758        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AbsFn));
759        let ctx = interp(&wb);
760        let e = lit(LiteralValue::Error(ExcelError::from_error_string(
761            "#VALUE!",
762        )));
763        let f = ctx.context.get_function("", "ABS").unwrap();
764        match f
765            .dispatch(
766                &[ArgumentHandle::new(&e, &ctx)],
767                &ctx.function_context(None),
768            )
769            .unwrap()
770        {
771            LiteralValue::Error(er) => assert_eq!(er, "#VALUE!"),
772            _ => panic!(),
773        }
774    }
775
776    // SIGN
777    #[test]
778    fn sign_neg_zero_pos() {
779        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SignFn));
780        let ctx = interp(&wb);
781        let f = ctx.context.get_function("", "SIGN").unwrap();
782        let neg = lit(LiteralValue::Number(-3.2));
783        let zero = lit(LiteralValue::Int(0));
784        let pos = lit(LiteralValue::Int(9));
785        assert_eq!(
786            f.dispatch(
787                &[ArgumentHandle::new(&neg, &ctx)],
788                &ctx.function_context(None)
789            )
790            .unwrap(),
791            LiteralValue::Number(-1.0)
792        );
793        assert_eq!(
794            f.dispatch(
795                &[ArgumentHandle::new(&zero, &ctx)],
796                &ctx.function_context(None)
797            )
798            .unwrap(),
799            LiteralValue::Number(0.0)
800        );
801        assert_eq!(
802            f.dispatch(
803                &[ArgumentHandle::new(&pos, &ctx)],
804                &ctx.function_context(None)
805            )
806            .unwrap(),
807            LiteralValue::Number(1.0)
808        );
809    }
810    #[test]
811    fn sign_error_passthrough() {
812        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SignFn));
813        let ctx = interp(&wb);
814        let e = lit(LiteralValue::Error(ExcelError::from_error_string(
815            "#DIV/0!",
816        )));
817        let f = ctx.context.get_function("", "SIGN").unwrap();
818        match f
819            .dispatch(
820                &[ArgumentHandle::new(&e, &ctx)],
821                &ctx.function_context(None),
822            )
823            .unwrap()
824        {
825            LiteralValue::Error(er) => assert_eq!(er, "#DIV/0!"),
826            _ => panic!(),
827        }
828    }
829
830    // INT
831    #[test]
832    fn int_floor_negative() {
833        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IntFn));
834        let ctx = interp(&wb);
835        let f = ctx.context.get_function("", "INT").unwrap();
836        let n = lit(LiteralValue::Number(-3.2));
837        assert_eq!(
838            f.dispatch(
839                &[ArgumentHandle::new(&n, &ctx)],
840                &ctx.function_context(None)
841            )
842            .unwrap(),
843            LiteralValue::Number(-4.0)
844        );
845    }
846    #[test]
847    fn int_floor_positive() {
848        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IntFn));
849        let ctx = interp(&wb);
850        let f = ctx.context.get_function("", "INT").unwrap();
851        let n = lit(LiteralValue::Number(3.7));
852        assert_eq!(
853            f.dispatch(
854                &[ArgumentHandle::new(&n, &ctx)],
855                &ctx.function_context(None)
856            )
857            .unwrap(),
858            LiteralValue::Number(3.0)
859        );
860    }
861
862    // TRUNC
863    #[test]
864    fn trunc_digits_positive_and_negative() {
865        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TruncFn));
866        let ctx = interp(&wb);
867        let f = ctx.context.get_function("", "TRUNC").unwrap();
868        let n = lit(LiteralValue::Number(12.3456));
869        let d2 = lit(LiteralValue::Int(2));
870        let dneg1 = lit(LiteralValue::Int(-1));
871        assert_eq!(
872            f.dispatch(
873                &[
874                    ArgumentHandle::new(&n, &ctx),
875                    ArgumentHandle::new(&d2, &ctx)
876                ],
877                &ctx.function_context(None)
878            )
879            .unwrap(),
880            LiteralValue::Number(12.34)
881        );
882        assert_eq!(
883            f.dispatch(
884                &[
885                    ArgumentHandle::new(&n, &ctx),
886                    ArgumentHandle::new(&dneg1, &ctx)
887                ],
888                &ctx.function_context(None)
889            )
890            .unwrap(),
891            LiteralValue::Number(10.0)
892        );
893    }
894    #[test]
895    fn trunc_default_zero_digits() {
896        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TruncFn));
897        let ctx = interp(&wb);
898        let f = ctx.context.get_function("", "TRUNC").unwrap();
899        let n = lit(LiteralValue::Number(-12.999));
900        assert_eq!(
901            f.dispatch(
902                &[ArgumentHandle::new(&n, &ctx)],
903                &ctx.function_context(None)
904            )
905            .unwrap(),
906            LiteralValue::Number(-12.0)
907        );
908    }
909
910    // ROUND
911    #[test]
912    fn round_half_away_positive_and_negative() {
913        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundFn));
914        let ctx = interp(&wb);
915        let f = ctx.context.get_function("", "ROUND").unwrap();
916        let p = lit(LiteralValue::Number(2.5));
917        let n = lit(LiteralValue::Number(-2.5));
918        let d0 = lit(LiteralValue::Int(0));
919        assert_eq!(
920            f.dispatch(
921                &[
922                    ArgumentHandle::new(&p, &ctx),
923                    ArgumentHandle::new(&d0, &ctx)
924                ],
925                &ctx.function_context(None)
926            )
927            .unwrap(),
928            LiteralValue::Number(3.0)
929        );
930        assert_eq!(
931            f.dispatch(
932                &[
933                    ArgumentHandle::new(&n, &ctx),
934                    ArgumentHandle::new(&d0, &ctx)
935                ],
936                &ctx.function_context(None)
937            )
938            .unwrap(),
939            LiteralValue::Number(-3.0)
940        );
941    }
942    #[test]
943    fn round_digits_positive() {
944        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundFn));
945        let ctx = interp(&wb);
946        let f = ctx.context.get_function("", "ROUND").unwrap();
947        let n = lit(LiteralValue::Number(1.2345));
948        let d = lit(LiteralValue::Int(3));
949        assert_eq!(
950            f.dispatch(
951                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
952                &ctx.function_context(None)
953            )
954            .unwrap(),
955            LiteralValue::Number(1.235)
956        );
957    }
958
959    // ROUNDDOWN
960    #[test]
961    fn rounddown_truncates() {
962        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundDownFn));
963        let ctx = interp(&wb);
964        let f = ctx.context.get_function("", "ROUNDDOWN").unwrap();
965        let n = lit(LiteralValue::Number(1.299));
966        let d = lit(LiteralValue::Int(2));
967        assert_eq!(
968            f.dispatch(
969                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
970                &ctx.function_context(None)
971            )
972            .unwrap(),
973            LiteralValue::Number(1.29)
974        );
975    }
976    #[test]
977    fn rounddown_negative_number() {
978        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundDownFn));
979        let ctx = interp(&wb);
980        let f = ctx.context.get_function("", "ROUNDDOWN").unwrap();
981        let n = lit(LiteralValue::Number(-1.299));
982        let d = lit(LiteralValue::Int(2));
983        assert_eq!(
984            f.dispatch(
985                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
986                &ctx.function_context(None)
987            )
988            .unwrap(),
989            LiteralValue::Number(-1.29)
990        );
991    }
992
993    // ROUNDUP
994    #[test]
995    fn roundup_away_from_zero() {
996        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundUpFn));
997        let ctx = interp(&wb);
998        let f = ctx.context.get_function("", "ROUNDUP").unwrap();
999        let n = lit(LiteralValue::Number(1.001));
1000        let d = lit(LiteralValue::Int(2));
1001        assert_eq!(
1002            f.dispatch(
1003                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1004                &ctx.function_context(None)
1005            )
1006            .unwrap(),
1007            LiteralValue::Number(1.01)
1008        );
1009    }
1010    #[test]
1011    fn roundup_negative() {
1012        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundUpFn));
1013        let ctx = interp(&wb);
1014        let f = ctx.context.get_function("", "ROUNDUP").unwrap();
1015        let n = lit(LiteralValue::Number(-1.001));
1016        let d = lit(LiteralValue::Int(2));
1017        assert_eq!(
1018            f.dispatch(
1019                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1020                &ctx.function_context(None)
1021            )
1022            .unwrap(),
1023            LiteralValue::Number(-1.01)
1024        );
1025    }
1026
1027    // MOD
1028    #[test]
1029    fn mod_positive_negative_cases() {
1030        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModFn));
1031        let ctx = interp(&wb);
1032        let f = ctx.context.get_function("", "MOD").unwrap();
1033        let a = lit(LiteralValue::Int(-3));
1034        let b = lit(LiteralValue::Int(2));
1035        let out = f
1036            .dispatch(
1037                &[ArgumentHandle::new(&a, &ctx), ArgumentHandle::new(&b, &ctx)],
1038                &ctx.function_context(None),
1039            )
1040            .unwrap();
1041        assert_eq!(out, LiteralValue::Number(1.0));
1042        let a2 = lit(LiteralValue::Int(3));
1043        let b2 = lit(LiteralValue::Int(-2));
1044        let out2 = f
1045            .dispatch(
1046                &[
1047                    ArgumentHandle::new(&a2, &ctx),
1048                    ArgumentHandle::new(&b2, &ctx),
1049                ],
1050                &ctx.function_context(None),
1051            )
1052            .unwrap();
1053        assert_eq!(out2, LiteralValue::Number(-1.0));
1054    }
1055    #[test]
1056    fn mod_div_by_zero_error() {
1057        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModFn));
1058        let ctx = interp(&wb);
1059        let f = ctx.context.get_function("", "MOD").unwrap();
1060        let a = lit(LiteralValue::Int(5));
1061        let zero = lit(LiteralValue::Int(0));
1062        match f
1063            .dispatch(
1064                &[
1065                    ArgumentHandle::new(&a, &ctx),
1066                    ArgumentHandle::new(&zero, &ctx),
1067                ],
1068                &ctx.function_context(None),
1069            )
1070            .unwrap()
1071        {
1072            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
1073            _ => panic!(),
1074        }
1075    }
1076
1077    // SQRT domain
1078    #[test]
1079    fn sqrt_basic_and_domain() {
1080        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SqrtFn));
1081        let ctx = interp(&wb);
1082        let f = ctx.context.get_function("", "SQRT").unwrap();
1083        let n = lit(LiteralValue::Number(9.0));
1084        let out = f
1085            .dispatch(
1086                &[ArgumentHandle::new(&n, &ctx)],
1087                &ctx.function_context(None),
1088            )
1089            .unwrap();
1090        assert_eq!(out, LiteralValue::Number(3.0));
1091        let neg = lit(LiteralValue::Number(-1.0));
1092        let out2 = f
1093            .dispatch(
1094                &[ArgumentHandle::new(&neg, &ctx)],
1095                &ctx.function_context(None),
1096            )
1097            .unwrap();
1098        assert!(matches!(out2, LiteralValue::Error(_)));
1099    }
1100
1101    #[test]
1102    fn power_fractional_negative_domain() {
1103        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PowerFn));
1104        let ctx = interp(&wb);
1105        let f = ctx.context.get_function("", "POWER").unwrap();
1106        let a = lit(LiteralValue::Number(-4.0));
1107        let half = lit(LiteralValue::Number(0.5));
1108        let out = f
1109            .dispatch(
1110                &[
1111                    ArgumentHandle::new(&a, &ctx),
1112                    ArgumentHandle::new(&half, &ctx),
1113                ],
1114                &ctx.function_context(None),
1115            )
1116            .unwrap();
1117        assert!(matches!(out, LiteralValue::Error(_))); // complex -> #NUM!
1118    }
1119
1120    #[test]
1121    fn log_variants() {
1122        let wb = TestWorkbook::new()
1123            .with_function(std::sync::Arc::new(LogFn))
1124            .with_function(std::sync::Arc::new(Log10Fn))
1125            .with_function(std::sync::Arc::new(LnFn));
1126        let ctx = interp(&wb);
1127        let logf = ctx.context.get_function("", "LOG").unwrap();
1128        let log10f = ctx.context.get_function("", "LOG10").unwrap();
1129        let lnf = ctx.context.get_function("", "LN").unwrap();
1130        let n = lit(LiteralValue::Number(100.0));
1131        let base = lit(LiteralValue::Number(10.0));
1132        assert_eq!(
1133            logf.dispatch(
1134                &[
1135                    ArgumentHandle::new(&n, &ctx),
1136                    ArgumentHandle::new(&base, &ctx)
1137                ],
1138                &ctx.function_context(None)
1139            )
1140            .unwrap(),
1141            LiteralValue::Number(2.0)
1142        );
1143        assert_eq!(
1144            log10f
1145                .dispatch(
1146                    &[ArgumentHandle::new(&n, &ctx)],
1147                    &ctx.function_context(None)
1148                )
1149                .unwrap(),
1150            LiteralValue::Number(2.0)
1151        );
1152        assert_eq!(
1153            lnf.dispatch(
1154                &[ArgumentHandle::new(&n, &ctx)],
1155                &ctx.function_context(None)
1156            )
1157            .unwrap(),
1158            LiteralValue::Number(100.0f64.ln())
1159        );
1160    }
1161    #[test]
1162    fn ceiling_floor_basic() {
1163        let wb = TestWorkbook::new()
1164            .with_function(std::sync::Arc::new(CeilingFn))
1165            .with_function(std::sync::Arc::new(FloorFn))
1166            .with_function(std::sync::Arc::new(CeilingMathFn))
1167            .with_function(std::sync::Arc::new(FloorMathFn));
1168        let ctx = interp(&wb);
1169        let c = ctx.context.get_function("", "CEILING").unwrap();
1170        let f = ctx.context.get_function("", "FLOOR").unwrap();
1171        let n = lit(LiteralValue::Number(5.1));
1172        let sig = lit(LiteralValue::Number(2.0));
1173        assert_eq!(
1174            c.dispatch(
1175                &[
1176                    ArgumentHandle::new(&n, &ctx),
1177                    ArgumentHandle::new(&sig, &ctx)
1178                ],
1179                &ctx.function_context(None)
1180            )
1181            .unwrap(),
1182            LiteralValue::Number(6.0)
1183        );
1184        assert_eq!(
1185            f.dispatch(
1186                &[
1187                    ArgumentHandle::new(&n, &ctx),
1188                    ArgumentHandle::new(&sig, &ctx)
1189                ],
1190                &ctx.function_context(None)
1191            )
1192            .unwrap(),
1193            LiteralValue::Number(4.0)
1194        );
1195    }
1196}