Skip to main content

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