Skip to main content

formualizer_eval/builtins/math/
numeric.rs

1use super::super::utils::{
2    ARG_NUM_LENIENT_ONE, ARG_NUM_LENIENT_TWO, ARG_RANGE_NUM_LENIENT_ONE, coerce_num,
3};
4use crate::args::ArgSchema;
5use crate::function::Function;
6use crate::traits::{ArgumentHandle, FunctionContext};
7use formualizer_common::{ExcelError, LiteralValue};
8use formualizer_macros::func_caps;
9
10#[derive(Debug)]
11pub struct AbsFn;
12impl Function for AbsFn {
13    func_caps!(PURE);
14    fn name(&self) -> &'static str {
15        "ABS"
16    }
17    fn min_args(&self) -> usize {
18        1
19    }
20    fn arg_schema(&self) -> &'static [ArgSchema] {
21        &ARG_NUM_LENIENT_ONE[..]
22    }
23    fn eval<'a, 'b, 'c>(
24        &self,
25        args: &'c [ArgumentHandle<'a, 'b>],
26        _: &dyn FunctionContext<'b>,
27    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
28        let v = args[0].value()?.into_literal();
29        match v {
30            LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
31            other => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
32                coerce_num(&other)?.abs(),
33            ))),
34        }
35    }
36}
37
38#[derive(Debug)]
39pub struct SignFn;
40impl Function for SignFn {
41    func_caps!(PURE);
42    fn name(&self) -> &'static str {
43        "SIGN"
44    }
45    fn min_args(&self) -> usize {
46        1
47    }
48    fn arg_schema(&self) -> &'static [ArgSchema] {
49        &ARG_NUM_LENIENT_ONE[..]
50    }
51    fn eval<'a, 'b, 'c>(
52        &self,
53        args: &'c [ArgumentHandle<'a, 'b>],
54        _: &dyn FunctionContext<'b>,
55    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
56        let v = args[0].value()?.into_literal();
57        match v {
58            LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
59            other => {
60                let n = coerce_num(&other)?;
61                Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
62                    if n > 0.0 {
63                        1.0
64                    } else if n < 0.0 {
65                        -1.0
66                    } else {
67                        0.0
68                    },
69                )))
70            }
71        }
72    }
73}
74
75#[derive(Debug)]
76pub struct IntFn; // floor toward -inf
77impl Function for IntFn {
78    func_caps!(PURE);
79    fn name(&self) -> &'static str {
80        "INT"
81    }
82    fn min_args(&self) -> usize {
83        1
84    }
85    fn arg_schema(&self) -> &'static [ArgSchema] {
86        &ARG_NUM_LENIENT_ONE[..]
87    }
88    fn eval<'a, 'b, 'c>(
89        &self,
90        args: &'c [ArgumentHandle<'a, 'b>],
91        _: &dyn FunctionContext<'b>,
92    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
93        let v = args[0].value()?.into_literal();
94        match v {
95            LiteralValue::Error(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
96            other => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
97                coerce_num(&other)?.floor(),
98            ))),
99        }
100    }
101}
102
103#[derive(Debug)]
104pub struct TruncFn; // truncate toward zero
105impl Function for TruncFn {
106    func_caps!(PURE);
107    fn name(&self) -> &'static str {
108        "TRUNC"
109    }
110    fn min_args(&self) -> usize {
111        1
112    }
113    fn variadic(&self) -> bool {
114        true
115    }
116    fn arg_schema(&self) -> &'static [ArgSchema] {
117        &ARG_NUM_LENIENT_TWO[..]
118    }
119    fn eval<'a, 'b, 'c>(
120        &self,
121        args: &'c [ArgumentHandle<'a, 'b>],
122        _: &dyn FunctionContext<'b>,
123    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
124        if args.is_empty() || args.len() > 2 {
125            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
126                ExcelError::new_value(),
127            )));
128        }
129        let mut n = match args[0].value()?.into_literal() {
130            LiteralValue::Error(e) => {
131                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
132            }
133            other => coerce_num(&other)?,
134        };
135        let digits: i32 = if args.len() == 2 {
136            match args[1].value()?.into_literal() {
137                LiteralValue::Error(e) => {
138                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
139                }
140                other => coerce_num(&other)? as i32,
141            }
142        } else {
143            0
144        };
145        if digits >= 0 {
146            let f = 10f64.powi(digits);
147            n = (n * f).trunc() / f;
148        } else {
149            let f = 10f64.powi(-digits);
150            n = (n / f).trunc() * f;
151        }
152        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(n)))
153    }
154}
155
156#[derive(Debug)]
157pub struct RoundFn; // ROUND(number, digits)
158impl Function for RoundFn {
159    func_caps!(PURE);
160    fn name(&self) -> &'static str {
161        "ROUND"
162    }
163    fn min_args(&self) -> usize {
164        2
165    }
166    fn arg_schema(&self) -> &'static [ArgSchema] {
167        &ARG_NUM_LENIENT_TWO[..]
168    }
169    fn eval<'a, 'b, 'c>(
170        &self,
171        args: &'c [ArgumentHandle<'a, 'b>],
172        _: &dyn FunctionContext<'b>,
173    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
174        let n = match args[0].value()?.into_literal() {
175            LiteralValue::Error(e) => {
176                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
177            }
178            other => coerce_num(&other)?,
179        };
180        let digits = match args[1].value()?.into_literal() {
181            LiteralValue::Error(e) => {
182                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
183            }
184            other => coerce_num(&other)? as i32,
185        };
186        let f = 10f64.powi(digits.abs());
187        let out = if digits >= 0 {
188            (n * f).round() / f
189        } else {
190            (n / f).round() * f
191        };
192        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(out)))
193    }
194}
195
196#[derive(Debug)]
197pub struct RoundDownFn; // toward zero
198impl Function for RoundDownFn {
199    func_caps!(PURE);
200    fn name(&self) -> &'static str {
201        "ROUNDDOWN"
202    }
203    fn min_args(&self) -> usize {
204        2
205    }
206    fn arg_schema(&self) -> &'static [ArgSchema] {
207        &ARG_NUM_LENIENT_TWO[..]
208    }
209    fn eval<'a, 'b, 'c>(
210        &self,
211        args: &'c [ArgumentHandle<'a, 'b>],
212        _: &dyn FunctionContext<'b>,
213    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
214        let n = match args[0].value()?.into_literal() {
215            LiteralValue::Error(e) => {
216                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
217            }
218            other => coerce_num(&other)?,
219        };
220        let digits = match args[1].value()?.into_literal() {
221            LiteralValue::Error(e) => {
222                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
223            }
224            other => coerce_num(&other)? as i32,
225        };
226        let f = 10f64.powi(digits.abs());
227        let out = if digits >= 0 {
228            (n * f).trunc() / f
229        } else {
230            (n / f).trunc() * f
231        };
232        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(out)))
233    }
234}
235
236#[derive(Debug)]
237pub struct RoundUpFn; // away from zero
238impl Function for RoundUpFn {
239    func_caps!(PURE);
240    fn name(&self) -> &'static str {
241        "ROUNDUP"
242    }
243    fn min_args(&self) -> usize {
244        2
245    }
246    fn arg_schema(&self) -> &'static [ArgSchema] {
247        &ARG_NUM_LENIENT_TWO[..]
248    }
249    fn eval<'a, 'b, 'c>(
250        &self,
251        args: &'c [ArgumentHandle<'a, 'b>],
252        _: &dyn FunctionContext<'b>,
253    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
254        let n = match args[0].value()?.into_literal() {
255            LiteralValue::Error(e) => {
256                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
257            }
258            other => coerce_num(&other)?,
259        };
260        let digits = match args[1].value()?.into_literal() {
261            LiteralValue::Error(e) => {
262                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
263            }
264            other => coerce_num(&other)? as i32,
265        };
266        let f = 10f64.powi(digits.abs());
267        let mut scaled = if digits >= 0 { n * f } else { n / f };
268        if scaled > 0.0 {
269            scaled = scaled.ceil();
270        } else {
271            scaled = scaled.floor();
272        }
273        let out = if digits >= 0 { scaled / f } else { scaled * f };
274        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(out)))
275    }
276}
277
278#[derive(Debug)]
279pub struct ModFn; // MOD(a,b)
280impl Function for ModFn {
281    func_caps!(PURE);
282    fn name(&self) -> &'static str {
283        "MOD"
284    }
285    fn min_args(&self) -> usize {
286        2
287    }
288    fn arg_schema(&self) -> &'static [ArgSchema] {
289        &ARG_NUM_LENIENT_TWO[..]
290    }
291    fn eval<'a, 'b, 'c>(
292        &self,
293        args: &'c [ArgumentHandle<'a, 'b>],
294        _: &dyn FunctionContext<'b>,
295    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
296        let x = match args[0].value()?.into_literal() {
297            LiteralValue::Error(e) => {
298                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
299            }
300            other => coerce_num(&other)?,
301        };
302        let y = match args[1].value()?.into_literal() {
303            LiteralValue::Error(e) => {
304                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
305            }
306            other => coerce_num(&other)?,
307        };
308        if y == 0.0 {
309            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
310                ExcelError::from_error_string("#DIV/0!"),
311            )));
312        }
313        let m = x % y;
314        let mut r = if m == 0.0 {
315            0.0
316        } else if (y > 0.0 && m < 0.0) || (y < 0.0 && m > 0.0) {
317            m + y
318        } else {
319            m
320        };
321        if r == -0.0 {
322            r = 0.0;
323        }
324        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(r)))
325    }
326}
327
328/* ───────────────────── Additional Math / Rounding ───────────────────── */
329
330#[derive(Debug)]
331pub struct CeilingFn; // CEILING(number, [significance]) legacy semantics simplified
332impl Function for CeilingFn {
333    func_caps!(PURE);
334    fn name(&self) -> &'static str {
335        "CEILING"
336    }
337    fn min_args(&self) -> usize {
338        1
339    }
340    fn variadic(&self) -> bool {
341        true
342    }
343    fn arg_schema(&self) -> &'static [ArgSchema] {
344        &ARG_NUM_LENIENT_TWO[..]
345    }
346    fn eval<'a, 'b, 'c>(
347        &self,
348        args: &'c [ArgumentHandle<'a, 'b>],
349        _: &dyn FunctionContext<'b>,
350    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
351        if args.is_empty() || args.len() > 2 {
352            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
353                ExcelError::new_value(),
354            )));
355        }
356        let n = match args[0].value()?.into_literal() {
357            LiteralValue::Error(e) => {
358                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
359            }
360            other => coerce_num(&other)?,
361        };
362        let mut sig = if args.len() == 2 {
363            match args[1].value()?.into_literal() {
364                LiteralValue::Error(e) => {
365                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
366                }
367                other => coerce_num(&other)?,
368            }
369        } else {
370            1.0
371        };
372        if sig == 0.0 {
373            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
374                ExcelError::from_error_string("#DIV/0!"),
375            )));
376        }
377        if sig < 0.0 {
378            sig = sig.abs(); /* Excel nuances: #NUM! when sign mismatch; simplified TODO */
379        }
380        let k = (n / sig).ceil();
381        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
382            k * sig,
383        )))
384    }
385}
386
387#[derive(Debug)]
388pub struct CeilingMathFn; // CEILING.MATH(number,[significance],[mode])
389impl Function for CeilingMathFn {
390    func_caps!(PURE);
391    fn name(&self) -> &'static str {
392        "CEILING.MATH"
393    }
394    fn min_args(&self) -> usize {
395        1
396    }
397    fn variadic(&self) -> bool {
398        true
399    }
400    fn arg_schema(&self) -> &'static [ArgSchema] {
401        &ARG_NUM_LENIENT_TWO[..]
402    } // allow up to 3 handled manually
403    fn eval<'a, 'b, 'c>(
404        &self,
405        args: &'c [ArgumentHandle<'a, 'b>],
406        _: &dyn FunctionContext<'b>,
407    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
408        if args.is_empty() || args.len() > 3 {
409            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
410                ExcelError::new_value(),
411            )));
412        }
413        let n = match args[0].value()?.into_literal() {
414            LiteralValue::Error(e) => {
415                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
416            }
417            other => coerce_num(&other)?,
418        };
419        let sig = if args.len() >= 2 {
420            match args[1].value()?.into_literal() {
421                LiteralValue::Error(e) => {
422                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
423                }
424                other => {
425                    let v = coerce_num(&other)?;
426                    if v == 0.0 { 1.0 } else { v.abs() }
427                }
428            }
429        } else {
430            1.0
431        };
432        let mode_nonzero = if args.len() == 3 {
433            match args[2].value()?.into_literal() {
434                LiteralValue::Error(e) => {
435                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
436                }
437                other => coerce_num(&other)? != 0.0,
438            }
439        } else {
440            false
441        };
442        let result = if n >= 0.0 {
443            (n / sig).ceil() * sig
444        } else if mode_nonzero {
445            (n / sig).floor() * sig /* away from zero */
446        } else {
447            (n / sig).ceil() * sig /* toward +inf (less negative) */
448        };
449        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
450            result,
451        )))
452    }
453}
454
455#[derive(Debug)]
456pub struct FloorFn; // FLOOR(number,[significance])
457impl Function for FloorFn {
458    func_caps!(PURE);
459    fn name(&self) -> &'static str {
460        "FLOOR"
461    }
462    fn min_args(&self) -> usize {
463        1
464    }
465    fn variadic(&self) -> bool {
466        true
467    }
468    fn arg_schema(&self) -> &'static [ArgSchema] {
469        &ARG_NUM_LENIENT_TWO[..]
470    }
471    fn eval<'a, 'b, 'c>(
472        &self,
473        args: &'c [ArgumentHandle<'a, 'b>],
474        _: &dyn FunctionContext<'b>,
475    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
476        if args.is_empty() || args.len() > 2 {
477            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
478                ExcelError::new_value(),
479            )));
480        }
481        let n = match args[0].value()?.into_literal() {
482            LiteralValue::Error(e) => {
483                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
484            }
485            other => coerce_num(&other)?,
486        };
487        let mut sig = if args.len() == 2 {
488            match args[1].value()?.into_literal() {
489                LiteralValue::Error(e) => {
490                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
491                }
492                other => coerce_num(&other)?,
493            }
494        } else {
495            1.0
496        };
497        if sig == 0.0 {
498            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
499                ExcelError::from_error_string("#DIV/0!"),
500            )));
501        }
502        if sig < 0.0 {
503            sig = sig.abs();
504        }
505        let k = (n / sig).floor();
506        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
507            k * sig,
508        )))
509    }
510}
511
512#[derive(Debug)]
513pub struct FloorMathFn; // FLOOR.MATH(number,[significance],[mode])
514impl Function for FloorMathFn {
515    func_caps!(PURE);
516    fn name(&self) -> &'static str {
517        "FLOOR.MATH"
518    }
519    fn min_args(&self) -> usize {
520        1
521    }
522    fn variadic(&self) -> bool {
523        true
524    }
525    fn arg_schema(&self) -> &'static [ArgSchema] {
526        &ARG_NUM_LENIENT_TWO[..]
527    }
528    fn eval<'a, 'b, 'c>(
529        &self,
530        args: &'c [ArgumentHandle<'a, 'b>],
531        _: &dyn FunctionContext<'b>,
532    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
533        if args.is_empty() || args.len() > 3 {
534            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
535                ExcelError::new_value(),
536            )));
537        }
538        let n = match args[0].value()?.into_literal() {
539            LiteralValue::Error(e) => {
540                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
541            }
542            other => coerce_num(&other)?,
543        };
544        let sig = if args.len() >= 2 {
545            match args[1].value()?.into_literal() {
546                LiteralValue::Error(e) => {
547                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
548                }
549                other => {
550                    let v = coerce_num(&other)?;
551                    if v == 0.0 { 1.0 } else { v.abs() }
552                }
553            }
554        } else {
555            1.0
556        };
557        let mode_nonzero = if args.len() == 3 {
558            match args[2].value()?.into_literal() {
559                LiteralValue::Error(e) => {
560                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
561                }
562                other => coerce_num(&other)? != 0.0,
563            }
564        } else {
565            false
566        };
567        let result = if n >= 0.0 {
568            (n / sig).floor() * sig
569        } else if mode_nonzero {
570            (n / sig).ceil() * sig
571        } else {
572            (n / sig).floor() * sig
573        };
574        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
575            result,
576        )))
577    }
578}
579
580#[derive(Debug)]
581pub struct SqrtFn; // SQRT(number)
582impl Function for SqrtFn {
583    func_caps!(PURE);
584    fn name(&self) -> &'static str {
585        "SQRT"
586    }
587    fn min_args(&self) -> usize {
588        1
589    }
590    fn arg_schema(&self) -> &'static [ArgSchema] {
591        &ARG_NUM_LENIENT_ONE[..]
592    }
593    fn eval<'a, 'b, 'c>(
594        &self,
595        args: &'c [ArgumentHandle<'a, 'b>],
596        _: &dyn FunctionContext<'b>,
597    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
598        let n = match args[0].value()?.into_literal() {
599            LiteralValue::Error(e) => {
600                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
601            }
602            other => coerce_num(&other)?,
603        };
604        if n < 0.0 {
605            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
606                ExcelError::new_num(),
607            )));
608        }
609        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
610            n.sqrt(),
611        )))
612    }
613}
614
615#[derive(Debug)]
616pub struct PowerFn; // POWER(number, power)
617impl Function for PowerFn {
618    func_caps!(PURE);
619    fn name(&self) -> &'static str {
620        "POWER"
621    }
622    fn min_args(&self) -> usize {
623        2
624    }
625    fn arg_schema(&self) -> &'static [ArgSchema] {
626        &ARG_NUM_LENIENT_TWO[..]
627    }
628    fn eval<'a, 'b, 'c>(
629        &self,
630        args: &'c [ArgumentHandle<'a, 'b>],
631        _: &dyn FunctionContext<'b>,
632    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
633        let base = match args[0].value()?.into_literal() {
634            LiteralValue::Error(e) => {
635                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
636            }
637            other => coerce_num(&other)?,
638        };
639        let expv = match args[1].value()?.into_literal() {
640            LiteralValue::Error(e) => {
641                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
642            }
643            other => coerce_num(&other)?,
644        };
645        if base < 0.0 && (expv.fract().abs() > 1e-12) {
646            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
647                ExcelError::new_num(),
648            )));
649        }
650        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
651            base.powf(expv),
652        )))
653    }
654}
655
656#[derive(Debug)]
657pub struct ExpFn; // EXP(number)
658impl Function for ExpFn {
659    func_caps!(PURE);
660    fn name(&self) -> &'static str {
661        "EXP"
662    }
663    fn min_args(&self) -> usize {
664        1
665    }
666    fn arg_schema(&self) -> &'static [ArgSchema] {
667        &ARG_NUM_LENIENT_ONE[..]
668    }
669    fn eval<'a, 'b, 'c>(
670        &self,
671        args: &'c [ArgumentHandle<'a, 'b>],
672        _: &dyn FunctionContext<'b>,
673    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
674        let n = match args[0].value()?.into_literal() {
675            LiteralValue::Error(e) => {
676                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
677            }
678            other => coerce_num(&other)?,
679        };
680        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
681            n.exp(),
682        )))
683    }
684}
685
686#[derive(Debug)]
687pub struct LnFn; // LN(number)
688impl Function for LnFn {
689    func_caps!(PURE);
690    fn name(&self) -> &'static str {
691        "LN"
692    }
693    fn min_args(&self) -> usize {
694        1
695    }
696    fn arg_schema(&self) -> &'static [ArgSchema] {
697        &ARG_NUM_LENIENT_ONE[..]
698    }
699    fn eval<'a, 'b, 'c>(
700        &self,
701        args: &'c [ArgumentHandle<'a, 'b>],
702        _: &dyn FunctionContext<'b>,
703    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
704        let n = match args[0].value()?.into_literal() {
705            LiteralValue::Error(e) => {
706                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
707            }
708            other => coerce_num(&other)?,
709        };
710        if n <= 0.0 {
711            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
712                ExcelError::new_num(),
713            )));
714        }
715        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
716            n.ln(),
717        )))
718    }
719}
720
721#[derive(Debug)]
722pub struct LogFn; // LOG(number,[base]) default base 10
723impl Function for LogFn {
724    func_caps!(PURE);
725    fn name(&self) -> &'static str {
726        "LOG"
727    }
728    fn min_args(&self) -> usize {
729        1
730    }
731    fn variadic(&self) -> bool {
732        true
733    }
734    fn arg_schema(&self) -> &'static [ArgSchema] {
735        &ARG_NUM_LENIENT_TWO[..]
736    }
737    fn eval<'a, 'b, 'c>(
738        &self,
739        args: &'c [ArgumentHandle<'a, 'b>],
740        _: &dyn FunctionContext<'b>,
741    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
742        if args.is_empty() || args.len() > 2 {
743            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
744                ExcelError::new_value(),
745            )));
746        }
747        let n = match args[0].value()?.into_literal() {
748            LiteralValue::Error(e) => {
749                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
750            }
751            other => coerce_num(&other)?,
752        };
753        let base = if args.len() == 2 {
754            match args[1].value()?.into_literal() {
755                LiteralValue::Error(e) => {
756                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
757                }
758                other => coerce_num(&other)?,
759            }
760        } else {
761            10.0
762        };
763        if n <= 0.0 || base <= 0.0 || (base - 1.0).abs() < 1e-12 {
764            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
765                ExcelError::new_num(),
766            )));
767        }
768        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
769            n.log(base),
770        )))
771    }
772}
773
774#[derive(Debug)]
775pub struct Log10Fn; // LOG10(number)
776impl Function for Log10Fn {
777    func_caps!(PURE);
778    fn name(&self) -> &'static str {
779        "LOG10"
780    }
781    fn min_args(&self) -> usize {
782        1
783    }
784    fn arg_schema(&self) -> &'static [ArgSchema] {
785        &ARG_NUM_LENIENT_ONE[..]
786    }
787    fn eval<'a, 'b, 'c>(
788        &self,
789        args: &'c [ArgumentHandle<'a, 'b>],
790        _: &dyn FunctionContext<'b>,
791    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
792        let n = match args[0].value()?.into_literal() {
793            LiteralValue::Error(e) => {
794                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
795            }
796            other => coerce_num(&other)?,
797        };
798        if n <= 0.0 {
799            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
800                ExcelError::new_num(),
801            )));
802        }
803        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
804            n.log10(),
805        )))
806    }
807}
808
809fn factorial_checked(n: i64) -> Option<f64> {
810    if !(0..=170).contains(&n) {
811        return None;
812    }
813    let mut out = 1.0;
814    for i in 2..=n {
815        out *= i as f64;
816    }
817    Some(out)
818}
819
820#[derive(Debug)]
821pub struct QuotientFn;
822impl Function for QuotientFn {
823    func_caps!(PURE);
824    fn name(&self) -> &'static str {
825        "QUOTIENT"
826    }
827    fn min_args(&self) -> usize {
828        2
829    }
830    fn arg_schema(&self) -> &'static [ArgSchema] {
831        &ARG_NUM_LENIENT_TWO[..]
832    }
833    fn eval<'a, 'b, 'c>(
834        &self,
835        args: &'c [ArgumentHandle<'a, 'b>],
836        _: &dyn FunctionContext<'b>,
837    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
838        let n = match args[0].value()?.into_literal() {
839            LiteralValue::Error(e) => {
840                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
841            }
842            other => coerce_num(&other)?,
843        };
844        let d = match args[1].value()?.into_literal() {
845            LiteralValue::Error(e) => {
846                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
847            }
848            other => coerce_num(&other)?,
849        };
850        if d == 0.0 {
851            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
852                ExcelError::new_div(),
853            )));
854        }
855        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
856            (n / d).trunc(),
857        )))
858    }
859}
860
861#[derive(Debug)]
862pub struct EvenFn;
863impl Function for EvenFn {
864    func_caps!(PURE);
865    fn name(&self) -> &'static str {
866        "EVEN"
867    }
868    fn min_args(&self) -> usize {
869        1
870    }
871    fn arg_schema(&self) -> &'static [ArgSchema] {
872        &ARG_NUM_LENIENT_ONE[..]
873    }
874    fn eval<'a, 'b, 'c>(
875        &self,
876        args: &'c [ArgumentHandle<'a, 'b>],
877        _: &dyn FunctionContext<'b>,
878    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
879        let number = match args[0].value()?.into_literal() {
880            LiteralValue::Error(e) => {
881                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
882            }
883            other => coerce_num(&other)?,
884        };
885        if number == 0.0 {
886            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)));
887        }
888
889        let sign = number.signum();
890        let mut v = number.abs().ceil() as i64;
891        if v % 2 != 0 {
892            v += 1;
893        }
894        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
895            sign * v as f64,
896        )))
897    }
898}
899
900#[derive(Debug)]
901pub struct OddFn;
902impl Function for OddFn {
903    func_caps!(PURE);
904    fn name(&self) -> &'static str {
905        "ODD"
906    }
907    fn min_args(&self) -> usize {
908        1
909    }
910    fn arg_schema(&self) -> &'static [ArgSchema] {
911        &ARG_NUM_LENIENT_ONE[..]
912    }
913    fn eval<'a, 'b, 'c>(
914        &self,
915        args: &'c [ArgumentHandle<'a, 'b>],
916        _: &dyn FunctionContext<'b>,
917    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
918        let number = match args[0].value()?.into_literal() {
919            LiteralValue::Error(e) => {
920                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
921            }
922            other => coerce_num(&other)?,
923        };
924
925        let sign = if number < 0.0 { -1.0 } else { 1.0 };
926        let mut v = number.abs().ceil() as i64;
927        if v % 2 == 0 {
928            v += 1;
929        }
930        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
931            sign * v as f64,
932        )))
933    }
934}
935
936#[derive(Debug)]
937pub struct SqrtPiFn;
938impl Function for SqrtPiFn {
939    func_caps!(PURE);
940    fn name(&self) -> &'static str {
941        "SQRTPI"
942    }
943    fn min_args(&self) -> usize {
944        1
945    }
946    fn arg_schema(&self) -> &'static [ArgSchema] {
947        &ARG_NUM_LENIENT_ONE[..]
948    }
949    fn eval<'a, 'b, 'c>(
950        &self,
951        args: &'c [ArgumentHandle<'a, 'b>],
952        _: &dyn FunctionContext<'b>,
953    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
954        let n = match args[0].value()?.into_literal() {
955            LiteralValue::Error(e) => {
956                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
957            }
958            other => coerce_num(&other)?,
959        };
960        if n < 0.0 {
961            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
962                ExcelError::new_num(),
963            )));
964        }
965        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
966            (n * std::f64::consts::PI).sqrt(),
967        )))
968    }
969}
970
971#[derive(Debug)]
972pub struct MultinomialFn;
973impl Function for MultinomialFn {
974    func_caps!(PURE);
975    fn name(&self) -> &'static str {
976        "MULTINOMIAL"
977    }
978    fn min_args(&self) -> usize {
979        1
980    }
981    fn variadic(&self) -> bool {
982        true
983    }
984    fn arg_schema(&self) -> &'static [ArgSchema] {
985        &ARG_NUM_LENIENT_ONE[..]
986    }
987    fn eval<'a, 'b, 'c>(
988        &self,
989        args: &'c [ArgumentHandle<'a, 'b>],
990        _ctx: &dyn FunctionContext<'b>,
991    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
992        let mut values: Vec<i64> = Vec::new();
993        for arg in args {
994            for value in arg.lazy_values_owned()? {
995                let n = match value {
996                    LiteralValue::Error(e) => {
997                        return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
998                    }
999                    other => coerce_num(&other)?.trunc() as i64,
1000                };
1001                if n < 0 {
1002                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1003                        ExcelError::new_num(),
1004                    )));
1005                }
1006                values.push(n);
1007            }
1008        }
1009
1010        let sum: i64 = values.iter().sum();
1011        let num = match factorial_checked(sum) {
1012            Some(v) => v,
1013            None => {
1014                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1015                    ExcelError::new_num(),
1016                )));
1017            }
1018        };
1019
1020        let mut den = 1.0;
1021        for n in values {
1022            let fact = match factorial_checked(n) {
1023                Some(v) => v,
1024                None => {
1025                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1026                        ExcelError::new_num(),
1027                    )));
1028                }
1029            };
1030            den *= fact;
1031        }
1032
1033        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1034            (num / den).round(),
1035        )))
1036    }
1037}
1038
1039#[derive(Debug)]
1040pub struct SeriesSumFn;
1041impl Function for SeriesSumFn {
1042    func_caps!(PURE);
1043    fn name(&self) -> &'static str {
1044        "SERIESSUM"
1045    }
1046    fn min_args(&self) -> usize {
1047        4
1048    }
1049    fn arg_schema(&self) -> &'static [ArgSchema] {
1050        use std::sync::LazyLock;
1051        static SCHEMA: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| {
1052            vec![
1053                ArgSchema::number_lenient_scalar(),
1054                ArgSchema::number_lenient_scalar(),
1055                ArgSchema::number_lenient_scalar(),
1056                ArgSchema::any(),
1057            ]
1058        });
1059        &SCHEMA[..]
1060    }
1061    fn eval<'a, 'b, 'c>(
1062        &self,
1063        args: &'c [ArgumentHandle<'a, 'b>],
1064        _ctx: &dyn FunctionContext<'b>,
1065    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1066        let x = match args[0].value()?.into_literal() {
1067            LiteralValue::Error(e) => {
1068                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1069            }
1070            other => coerce_num(&other)?,
1071        };
1072        let n = match args[1].value()?.into_literal() {
1073            LiteralValue::Error(e) => {
1074                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1075            }
1076            other => coerce_num(&other)?,
1077        };
1078        let m = match args[2].value()?.into_literal() {
1079            LiteralValue::Error(e) => {
1080                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1081            }
1082            other => coerce_num(&other)?,
1083        };
1084
1085        let mut coeffs: Vec<f64> = Vec::new();
1086        if let Ok(view) = args[3].range_view() {
1087            view.for_each_cell(&mut |cell| {
1088                match cell {
1089                    LiteralValue::Error(e) => return Err(e.clone()),
1090                    other => coeffs.push(coerce_num(other)?),
1091                }
1092                Ok(())
1093            })?;
1094        } else {
1095            match args[3].value()?.into_literal() {
1096                LiteralValue::Array(rows) => {
1097                    for row in rows {
1098                        for cell in row {
1099                            match cell {
1100                                LiteralValue::Error(e) => {
1101                                    return Ok(crate::traits::CalcValue::Scalar(
1102                                        LiteralValue::Error(e),
1103                                    ));
1104                                }
1105                                other => coeffs.push(coerce_num(&other)?),
1106                            }
1107                        }
1108                    }
1109                }
1110                LiteralValue::Error(e) => {
1111                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1112                }
1113                other => coeffs.push(coerce_num(&other)?),
1114            }
1115        }
1116
1117        let mut sum = 0.0;
1118        for (i, c) in coeffs.into_iter().enumerate() {
1119            sum += c * x.powf(n + (i as f64) * m);
1120        }
1121
1122        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(sum)))
1123    }
1124}
1125
1126#[derive(Debug)]
1127pub struct SumsqFn;
1128impl Function for SumsqFn {
1129    func_caps!(PURE, REDUCTION, NUMERIC_ONLY);
1130    fn name(&self) -> &'static str {
1131        "SUMSQ"
1132    }
1133    fn min_args(&self) -> usize {
1134        1
1135    }
1136    fn variadic(&self) -> bool {
1137        true
1138    }
1139    fn arg_schema(&self) -> &'static [ArgSchema] {
1140        &ARG_RANGE_NUM_LENIENT_ONE[..]
1141    }
1142    fn eval<'a, 'b, 'c>(
1143        &self,
1144        args: &'c [ArgumentHandle<'a, 'b>],
1145        _ctx: &dyn FunctionContext<'b>,
1146    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1147        let mut total = 0.0;
1148        for arg in args {
1149            if let Ok(view) = arg.range_view() {
1150                view.for_each_cell(&mut |cell| {
1151                    match cell {
1152                        LiteralValue::Error(e) => return Err(e.clone()),
1153                        LiteralValue::Number(n) => total += n * n,
1154                        LiteralValue::Int(i) => {
1155                            let n = *i as f64;
1156                            total += n * n;
1157                        }
1158                        LiteralValue::Date(d) => {
1159                            let n = crate::builtins::datetime::date_to_serial(d);
1160                            total += n * n;
1161                        }
1162                        LiteralValue::DateTime(dt) => {
1163                            let n = crate::builtins::datetime::datetime_to_serial(dt);
1164                            total += n * n;
1165                        }
1166                        LiteralValue::Time(t) => {
1167                            let n = crate::builtins::datetime::time_to_fraction(t);
1168                            total += n * n;
1169                        }
1170                        LiteralValue::Duration(d) => {
1171                            let n = d.num_seconds() as f64 / 86_400.0;
1172                            total += n * n;
1173                        }
1174                        _ => {}
1175                    }
1176                    Ok(())
1177                })?;
1178            } else {
1179                let v = arg.value()?.into_literal();
1180                match v {
1181                    LiteralValue::Error(e) => {
1182                        return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1183                    }
1184                    other => {
1185                        let n = coerce_num(&other)?;
1186                        total += n * n;
1187                    }
1188                }
1189            }
1190        }
1191        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1192            total,
1193        )))
1194    }
1195}
1196
1197#[derive(Debug)]
1198pub struct MroundFn;
1199impl Function for MroundFn {
1200    func_caps!(PURE);
1201    fn name(&self) -> &'static str {
1202        "MROUND"
1203    }
1204    fn min_args(&self) -> usize {
1205        2
1206    }
1207    fn arg_schema(&self) -> &'static [ArgSchema] {
1208        &ARG_NUM_LENIENT_TWO[..]
1209    }
1210    fn eval<'a, 'b, 'c>(
1211        &self,
1212        args: &'c [ArgumentHandle<'a, 'b>],
1213        _ctx: &dyn FunctionContext<'b>,
1214    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1215        let number = match args[0].value()?.into_literal() {
1216            LiteralValue::Error(e) => {
1217                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1218            }
1219            other => coerce_num(&other)?,
1220        };
1221        let multiple = match args[1].value()?.into_literal() {
1222            LiteralValue::Error(e) => {
1223                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1224            }
1225            other => coerce_num(&other)?,
1226        };
1227
1228        if multiple == 0.0 {
1229            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)));
1230        }
1231        if number != 0.0 && number.signum() != multiple.signum() {
1232            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1233                ExcelError::new_num(),
1234            )));
1235        }
1236
1237        let m = multiple.abs();
1238        let scaled = number.abs() / m;
1239        let rounded = (scaled + 0.5 + 1e-12).floor();
1240        let out = rounded * m * number.signum();
1241        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(out)))
1242    }
1243}
1244
1245fn roman_classic(mut n: u32) -> String {
1246    let table = [
1247        (1000, "M"),
1248        (900, "CM"),
1249        (500, "D"),
1250        (400, "CD"),
1251        (100, "C"),
1252        (90, "XC"),
1253        (50, "L"),
1254        (40, "XL"),
1255        (10, "X"),
1256        (9, "IX"),
1257        (5, "V"),
1258        (4, "IV"),
1259        (1, "I"),
1260    ];
1261
1262    let mut out = String::new();
1263    for (value, glyph) in table {
1264        while n >= value {
1265            n -= value;
1266            out.push_str(glyph);
1267        }
1268    }
1269    out
1270}
1271
1272fn roman_apply_form(classic: String, form: i64) -> String {
1273    match form {
1274        0 => classic,
1275        1 => classic
1276            .replace("CM", "LM")
1277            .replace("CD", "LD")
1278            .replace("XC", "VL")
1279            .replace("XL", "VL")
1280            .replace("IX", "IV"),
1281        2 => roman_apply_form(classic, 1)
1282            .replace("LD", "XD")
1283            .replace("LM", "XM")
1284            .replace("VLIV", "IX"),
1285        3 => roman_apply_form(classic, 2)
1286            .replace("XD", "VD")
1287            .replace("XM", "VM")
1288            .replace("IX", "IV"),
1289        4 => roman_apply_form(classic, 3)
1290            .replace("VDIV", "ID")
1291            .replace("VMIV", "IM"),
1292        _ => classic,
1293    }
1294}
1295
1296#[derive(Debug)]
1297pub struct RomanFn;
1298impl Function for RomanFn {
1299    func_caps!(PURE);
1300    fn name(&self) -> &'static str {
1301        "ROMAN"
1302    }
1303    fn min_args(&self) -> usize {
1304        1
1305    }
1306    fn variadic(&self) -> bool {
1307        true
1308    }
1309    fn arg_schema(&self) -> &'static [ArgSchema] {
1310        &ARG_NUM_LENIENT_TWO[..]
1311    }
1312    fn eval<'a, 'b, 'c>(
1313        &self,
1314        args: &'c [ArgumentHandle<'a, 'b>],
1315        _ctx: &dyn FunctionContext<'b>,
1316    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1317        if args.len() > 2 {
1318            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1319                ExcelError::new_value(),
1320            )));
1321        }
1322
1323        let number = match args[0].value()?.into_literal() {
1324            LiteralValue::Error(e) => {
1325                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1326            }
1327            other => coerce_num(&other)?.trunc() as i64,
1328        };
1329
1330        if !(0..=3999).contains(&number) {
1331            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1332                ExcelError::new_value(),
1333            )));
1334        }
1335        if number == 0 {
1336            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
1337                "".to_string(),
1338            )));
1339        }
1340
1341        let form = if args.len() >= 2 {
1342            match args[1].value()?.into_literal() {
1343                LiteralValue::Boolean(b) => {
1344                    if b {
1345                        0
1346                    } else {
1347                        4
1348                    }
1349                }
1350                LiteralValue::Number(n) => n.trunc() as i64,
1351                LiteralValue::Int(i) => i,
1352                LiteralValue::Empty => 0,
1353                LiteralValue::Error(e) => {
1354                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1355                }
1356                _ => {
1357                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1358                        ExcelError::new_value(),
1359                    )));
1360                }
1361            }
1362        } else {
1363            0
1364        };
1365
1366        if !(0..=4).contains(&form) {
1367            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1368                ExcelError::new_value(),
1369            )));
1370        }
1371
1372        let classic = roman_classic(number as u32);
1373        let text = roman_apply_form(classic, form);
1374        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(text)))
1375    }
1376}
1377
1378fn roman_digit_value(ch: char) -> Option<i64> {
1379    match ch {
1380        'I' => Some(1),
1381        'V' => Some(5),
1382        'X' => Some(10),
1383        'L' => Some(50),
1384        'C' => Some(100),
1385        'D' => Some(500),
1386        'M' => Some(1000),
1387        _ => None,
1388    }
1389}
1390
1391#[derive(Debug)]
1392pub struct ArabicFn;
1393impl Function for ArabicFn {
1394    func_caps!(PURE);
1395    fn name(&self) -> &'static str {
1396        "ARABIC"
1397    }
1398    fn min_args(&self) -> usize {
1399        1
1400    }
1401    fn arg_schema(&self) -> &'static [ArgSchema] {
1402        use std::sync::LazyLock;
1403        static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
1404        &ONE[..]
1405    }
1406    fn eval<'a, 'b, 'c>(
1407        &self,
1408        args: &'c [ArgumentHandle<'a, 'b>],
1409        _ctx: &dyn FunctionContext<'b>,
1410    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1411        let raw = match args[0].value()?.into_literal() {
1412            LiteralValue::Text(s) => s,
1413            LiteralValue::Empty => String::new(),
1414            LiteralValue::Error(e) => {
1415                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1416            }
1417            _ => {
1418                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1419                    ExcelError::new_value(),
1420                )));
1421            }
1422        };
1423
1424        let mut text = raw.trim().to_uppercase();
1425        if text.len() > 255 {
1426            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1427                ExcelError::new_value(),
1428            )));
1429        }
1430        if text.is_empty() {
1431            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)));
1432        }
1433
1434        let sign = if text.starts_with('-') {
1435            text.remove(0);
1436            -1.0
1437        } else {
1438            1.0
1439        };
1440
1441        if text.is_empty() {
1442            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1443                ExcelError::new_value(),
1444            )));
1445        }
1446
1447        let mut total = 0i64;
1448        let mut prev = 0i64;
1449        for ch in text.chars().rev() {
1450            let v = match roman_digit_value(ch) {
1451                Some(v) => v,
1452                None => {
1453                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1454                        ExcelError::new_value(),
1455                    )));
1456                }
1457            };
1458            if v < prev {
1459                total -= v;
1460            } else {
1461                total += v;
1462                prev = v;
1463            }
1464        }
1465
1466        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1467            sign * total as f64,
1468        )))
1469    }
1470}
1471
1472pub fn register_builtins() {
1473    use std::sync::Arc;
1474    crate::function_registry::register_function(Arc::new(AbsFn));
1475    crate::function_registry::register_function(Arc::new(SignFn));
1476    crate::function_registry::register_function(Arc::new(IntFn));
1477    crate::function_registry::register_function(Arc::new(TruncFn));
1478    crate::function_registry::register_function(Arc::new(RoundFn));
1479    crate::function_registry::register_function(Arc::new(RoundDownFn));
1480    crate::function_registry::register_function(Arc::new(RoundUpFn));
1481    crate::function_registry::register_function(Arc::new(ModFn));
1482    crate::function_registry::register_function(Arc::new(CeilingFn));
1483    crate::function_registry::register_function(Arc::new(CeilingMathFn));
1484    crate::function_registry::register_function(Arc::new(FloorFn));
1485    crate::function_registry::register_function(Arc::new(FloorMathFn));
1486    crate::function_registry::register_function(Arc::new(SqrtFn));
1487    crate::function_registry::register_function(Arc::new(PowerFn));
1488    crate::function_registry::register_function(Arc::new(ExpFn));
1489    crate::function_registry::register_function(Arc::new(LnFn));
1490    crate::function_registry::register_function(Arc::new(LogFn));
1491    crate::function_registry::register_function(Arc::new(Log10Fn));
1492    crate::function_registry::register_function(Arc::new(QuotientFn));
1493    crate::function_registry::register_function(Arc::new(EvenFn));
1494    crate::function_registry::register_function(Arc::new(OddFn));
1495    crate::function_registry::register_function(Arc::new(SqrtPiFn));
1496    crate::function_registry::register_function(Arc::new(MultinomialFn));
1497    crate::function_registry::register_function(Arc::new(SeriesSumFn));
1498    crate::function_registry::register_function(Arc::new(SumsqFn));
1499    crate::function_registry::register_function(Arc::new(MroundFn));
1500    crate::function_registry::register_function(Arc::new(RomanFn));
1501    crate::function_registry::register_function(Arc::new(ArabicFn));
1502}
1503
1504#[cfg(test)]
1505mod tests_numeric {
1506    use super::*;
1507    use crate::test_workbook::TestWorkbook;
1508    use crate::traits::ArgumentHandle;
1509    use formualizer_common::LiteralValue;
1510    use formualizer_parse::parser::{ASTNode, ASTNodeType};
1511
1512    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1513        wb.interpreter()
1514    }
1515    fn lit(v: LiteralValue) -> ASTNode {
1516        ASTNode::new(ASTNodeType::Literal(v), None)
1517    }
1518
1519    // ABS
1520    #[test]
1521    fn abs_basic() {
1522        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AbsFn));
1523        let ctx = interp(&wb);
1524        let n = lit(LiteralValue::Number(-5.5));
1525        let f = ctx.context.get_function("", "ABS").unwrap();
1526        assert_eq!(
1527            f.dispatch(
1528                &[ArgumentHandle::new(&n, &ctx)],
1529                &ctx.function_context(None)
1530            )
1531            .unwrap()
1532            .into_literal(),
1533            LiteralValue::Number(5.5)
1534        );
1535    }
1536    #[test]
1537    fn abs_error_passthrough() {
1538        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AbsFn));
1539        let ctx = interp(&wb);
1540        let e = lit(LiteralValue::Error(ExcelError::from_error_string(
1541            "#VALUE!",
1542        )));
1543        let f = ctx.context.get_function("", "ABS").unwrap();
1544        match f
1545            .dispatch(
1546                &[ArgumentHandle::new(&e, &ctx)],
1547                &ctx.function_context(None),
1548            )
1549            .unwrap()
1550            .into_literal()
1551        {
1552            LiteralValue::Error(er) => assert_eq!(er, "#VALUE!"),
1553            _ => panic!(),
1554        }
1555    }
1556
1557    // SIGN
1558    #[test]
1559    fn sign_neg_zero_pos() {
1560        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SignFn));
1561        let ctx = interp(&wb);
1562        let f = ctx.context.get_function("", "SIGN").unwrap();
1563        let neg = lit(LiteralValue::Number(-3.2));
1564        let zero = lit(LiteralValue::Int(0));
1565        let pos = lit(LiteralValue::Int(9));
1566        assert_eq!(
1567            f.dispatch(
1568                &[ArgumentHandle::new(&neg, &ctx)],
1569                &ctx.function_context(None)
1570            )
1571            .unwrap()
1572            .into_literal(),
1573            LiteralValue::Number(-1.0)
1574        );
1575        assert_eq!(
1576            f.dispatch(
1577                &[ArgumentHandle::new(&zero, &ctx)],
1578                &ctx.function_context(None)
1579            )
1580            .unwrap()
1581            .into_literal(),
1582            LiteralValue::Number(0.0)
1583        );
1584        assert_eq!(
1585            f.dispatch(
1586                &[ArgumentHandle::new(&pos, &ctx)],
1587                &ctx.function_context(None)
1588            )
1589            .unwrap()
1590            .into_literal(),
1591            LiteralValue::Number(1.0)
1592        );
1593    }
1594    #[test]
1595    fn sign_error_passthrough() {
1596        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SignFn));
1597        let ctx = interp(&wb);
1598        let e = lit(LiteralValue::Error(ExcelError::from_error_string(
1599            "#DIV/0!",
1600        )));
1601        let f = ctx.context.get_function("", "SIGN").unwrap();
1602        match f
1603            .dispatch(
1604                &[ArgumentHandle::new(&e, &ctx)],
1605                &ctx.function_context(None),
1606            )
1607            .unwrap()
1608            .into_literal()
1609        {
1610            LiteralValue::Error(er) => assert_eq!(er, "#DIV/0!"),
1611            _ => panic!(),
1612        }
1613    }
1614
1615    // INT
1616    #[test]
1617    fn int_floor_negative() {
1618        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IntFn));
1619        let ctx = interp(&wb);
1620        let f = ctx.context.get_function("", "INT").unwrap();
1621        let n = lit(LiteralValue::Number(-3.2));
1622        assert_eq!(
1623            f.dispatch(
1624                &[ArgumentHandle::new(&n, &ctx)],
1625                &ctx.function_context(None)
1626            )
1627            .unwrap()
1628            .into_literal(),
1629            LiteralValue::Number(-4.0)
1630        );
1631    }
1632    #[test]
1633    fn int_floor_positive() {
1634        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IntFn));
1635        let ctx = interp(&wb);
1636        let f = ctx.context.get_function("", "INT").unwrap();
1637        let n = lit(LiteralValue::Number(3.7));
1638        assert_eq!(
1639            f.dispatch(
1640                &[ArgumentHandle::new(&n, &ctx)],
1641                &ctx.function_context(None)
1642            )
1643            .unwrap()
1644            .into_literal(),
1645            LiteralValue::Number(3.0)
1646        );
1647    }
1648
1649    // TRUNC
1650    #[test]
1651    fn trunc_digits_positive_and_negative() {
1652        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TruncFn));
1653        let ctx = interp(&wb);
1654        let f = ctx.context.get_function("", "TRUNC").unwrap();
1655        let n = lit(LiteralValue::Number(12.3456));
1656        let d2 = lit(LiteralValue::Int(2));
1657        let dneg1 = lit(LiteralValue::Int(-1));
1658        assert_eq!(
1659            f.dispatch(
1660                &[
1661                    ArgumentHandle::new(&n, &ctx),
1662                    ArgumentHandle::new(&d2, &ctx)
1663                ],
1664                &ctx.function_context(None)
1665            )
1666            .unwrap()
1667            .into_literal(),
1668            LiteralValue::Number(12.34)
1669        );
1670        assert_eq!(
1671            f.dispatch(
1672                &[
1673                    ArgumentHandle::new(&n, &ctx),
1674                    ArgumentHandle::new(&dneg1, &ctx)
1675                ],
1676                &ctx.function_context(None)
1677            )
1678            .unwrap()
1679            .into_literal(),
1680            LiteralValue::Number(10.0)
1681        );
1682    }
1683    #[test]
1684    fn trunc_default_zero_digits() {
1685        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TruncFn));
1686        let ctx = interp(&wb);
1687        let f = ctx.context.get_function("", "TRUNC").unwrap();
1688        let n = lit(LiteralValue::Number(-12.999));
1689        assert_eq!(
1690            f.dispatch(
1691                &[ArgumentHandle::new(&n, &ctx)],
1692                &ctx.function_context(None)
1693            )
1694            .unwrap()
1695            .into_literal(),
1696            LiteralValue::Number(-12.0)
1697        );
1698    }
1699
1700    // ROUND
1701    #[test]
1702    fn round_half_away_positive_and_negative() {
1703        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundFn));
1704        let ctx = interp(&wb);
1705        let f = ctx.context.get_function("", "ROUND").unwrap();
1706        let p = lit(LiteralValue::Number(2.5));
1707        let n = lit(LiteralValue::Number(-2.5));
1708        let d0 = lit(LiteralValue::Int(0));
1709        assert_eq!(
1710            f.dispatch(
1711                &[
1712                    ArgumentHandle::new(&p, &ctx),
1713                    ArgumentHandle::new(&d0, &ctx)
1714                ],
1715                &ctx.function_context(None)
1716            )
1717            .unwrap()
1718            .into_literal(),
1719            LiteralValue::Number(3.0)
1720        );
1721        assert_eq!(
1722            f.dispatch(
1723                &[
1724                    ArgumentHandle::new(&n, &ctx),
1725                    ArgumentHandle::new(&d0, &ctx)
1726                ],
1727                &ctx.function_context(None)
1728            )
1729            .unwrap()
1730            .into_literal(),
1731            LiteralValue::Number(-3.0)
1732        );
1733    }
1734    #[test]
1735    fn round_digits_positive() {
1736        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundFn));
1737        let ctx = interp(&wb);
1738        let f = ctx.context.get_function("", "ROUND").unwrap();
1739        let n = lit(LiteralValue::Number(1.2345));
1740        let d = lit(LiteralValue::Int(3));
1741        assert_eq!(
1742            f.dispatch(
1743                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1744                &ctx.function_context(None)
1745            )
1746            .unwrap()
1747            .into_literal(),
1748            LiteralValue::Number(1.235)
1749        );
1750    }
1751
1752    // ROUNDDOWN
1753    #[test]
1754    fn rounddown_truncates() {
1755        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundDownFn));
1756        let ctx = interp(&wb);
1757        let f = ctx.context.get_function("", "ROUNDDOWN").unwrap();
1758        let n = lit(LiteralValue::Number(1.299));
1759        let d = lit(LiteralValue::Int(2));
1760        assert_eq!(
1761            f.dispatch(
1762                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1763                &ctx.function_context(None)
1764            )
1765            .unwrap()
1766            .into_literal(),
1767            LiteralValue::Number(1.29)
1768        );
1769    }
1770    #[test]
1771    fn rounddown_negative_number() {
1772        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundDownFn));
1773        let ctx = interp(&wb);
1774        let f = ctx.context.get_function("", "ROUNDDOWN").unwrap();
1775        let n = lit(LiteralValue::Number(-1.299));
1776        let d = lit(LiteralValue::Int(2));
1777        assert_eq!(
1778            f.dispatch(
1779                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1780                &ctx.function_context(None)
1781            )
1782            .unwrap()
1783            .into_literal(),
1784            LiteralValue::Number(-1.29)
1785        );
1786    }
1787
1788    // ROUNDUP
1789    #[test]
1790    fn roundup_away_from_zero() {
1791        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundUpFn));
1792        let ctx = interp(&wb);
1793        let f = ctx.context.get_function("", "ROUNDUP").unwrap();
1794        let n = lit(LiteralValue::Number(1.001));
1795        let d = lit(LiteralValue::Int(2));
1796        assert_eq!(
1797            f.dispatch(
1798                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1799                &ctx.function_context(None)
1800            )
1801            .unwrap()
1802            .into_literal(),
1803            LiteralValue::Number(1.01)
1804        );
1805    }
1806    #[test]
1807    fn roundup_negative() {
1808        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RoundUpFn));
1809        let ctx = interp(&wb);
1810        let f = ctx.context.get_function("", "ROUNDUP").unwrap();
1811        let n = lit(LiteralValue::Number(-1.001));
1812        let d = lit(LiteralValue::Int(2));
1813        assert_eq!(
1814            f.dispatch(
1815                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&d, &ctx)],
1816                &ctx.function_context(None)
1817            )
1818            .unwrap()
1819            .into_literal(),
1820            LiteralValue::Number(-1.01)
1821        );
1822    }
1823
1824    // MOD
1825    #[test]
1826    fn mod_positive_negative_cases() {
1827        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModFn));
1828        let ctx = interp(&wb);
1829        let f = ctx.context.get_function("", "MOD").unwrap();
1830        let a = lit(LiteralValue::Int(-3));
1831        let b = lit(LiteralValue::Int(2));
1832        let out = f
1833            .dispatch(
1834                &[ArgumentHandle::new(&a, &ctx), ArgumentHandle::new(&b, &ctx)],
1835                &ctx.function_context(None),
1836            )
1837            .unwrap();
1838        assert_eq!(out, LiteralValue::Number(1.0));
1839        let a2 = lit(LiteralValue::Int(3));
1840        let b2 = lit(LiteralValue::Int(-2));
1841        let out2 = f
1842            .dispatch(
1843                &[
1844                    ArgumentHandle::new(&a2, &ctx),
1845                    ArgumentHandle::new(&b2, &ctx),
1846                ],
1847                &ctx.function_context(None),
1848            )
1849            .unwrap();
1850        assert_eq!(out2, LiteralValue::Number(-1.0));
1851    }
1852    #[test]
1853    fn mod_div_by_zero_error() {
1854        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModFn));
1855        let ctx = interp(&wb);
1856        let f = ctx.context.get_function("", "MOD").unwrap();
1857        let a = lit(LiteralValue::Int(5));
1858        let zero = lit(LiteralValue::Int(0));
1859        match f
1860            .dispatch(
1861                &[
1862                    ArgumentHandle::new(&a, &ctx),
1863                    ArgumentHandle::new(&zero, &ctx),
1864                ],
1865                &ctx.function_context(None),
1866            )
1867            .unwrap()
1868            .into_literal()
1869        {
1870            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
1871            _ => panic!(),
1872        }
1873    }
1874
1875    // SQRT domain
1876    #[test]
1877    fn sqrt_basic_and_domain() {
1878        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SqrtFn));
1879        let ctx = interp(&wb);
1880        let f = ctx.context.get_function("", "SQRT").unwrap();
1881        let n = lit(LiteralValue::Number(9.0));
1882        let out = f
1883            .dispatch(
1884                &[ArgumentHandle::new(&n, &ctx)],
1885                &ctx.function_context(None),
1886            )
1887            .unwrap();
1888        assert_eq!(out, LiteralValue::Number(3.0));
1889        let neg = lit(LiteralValue::Number(-1.0));
1890        let out2 = f
1891            .dispatch(
1892                &[ArgumentHandle::new(&neg, &ctx)],
1893                &ctx.function_context(None),
1894            )
1895            .unwrap();
1896        assert!(matches!(out2.into_literal(), LiteralValue::Error(_)));
1897    }
1898
1899    #[test]
1900    fn power_fractional_negative_domain() {
1901        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PowerFn));
1902        let ctx = interp(&wb);
1903        let f = ctx.context.get_function("", "POWER").unwrap();
1904        let a = lit(LiteralValue::Number(-4.0));
1905        let half = lit(LiteralValue::Number(0.5));
1906        let out = f
1907            .dispatch(
1908                &[
1909                    ArgumentHandle::new(&a, &ctx),
1910                    ArgumentHandle::new(&half, &ctx),
1911                ],
1912                &ctx.function_context(None),
1913            )
1914            .unwrap();
1915        assert!(matches!(out.into_literal(), LiteralValue::Error(_))); // complex -> #NUM!
1916    }
1917
1918    #[test]
1919    fn log_variants() {
1920        let wb = TestWorkbook::new()
1921            .with_function(std::sync::Arc::new(LogFn))
1922            .with_function(std::sync::Arc::new(Log10Fn))
1923            .with_function(std::sync::Arc::new(LnFn));
1924        let ctx = interp(&wb);
1925        let logf = ctx.context.get_function("", "LOG").unwrap();
1926        let log10f = ctx.context.get_function("", "LOG10").unwrap();
1927        let lnf = ctx.context.get_function("", "LN").unwrap();
1928        let n = lit(LiteralValue::Number(100.0));
1929        let base = lit(LiteralValue::Number(10.0));
1930        assert_eq!(
1931            logf.dispatch(
1932                &[
1933                    ArgumentHandle::new(&n, &ctx),
1934                    ArgumentHandle::new(&base, &ctx)
1935                ],
1936                &ctx.function_context(None)
1937            )
1938            .unwrap()
1939            .into_literal(),
1940            LiteralValue::Number(2.0)
1941        );
1942        assert_eq!(
1943            log10f
1944                .dispatch(
1945                    &[ArgumentHandle::new(&n, &ctx)],
1946                    &ctx.function_context(None)
1947                )
1948                .unwrap()
1949                .into_literal(),
1950            LiteralValue::Number(2.0)
1951        );
1952        assert_eq!(
1953            lnf.dispatch(
1954                &[ArgumentHandle::new(&n, &ctx)],
1955                &ctx.function_context(None)
1956            )
1957            .unwrap()
1958            .into_literal(),
1959            LiteralValue::Number(100.0f64.ln())
1960        );
1961    }
1962    #[test]
1963    fn ceiling_floor_basic() {
1964        let wb = TestWorkbook::new()
1965            .with_function(std::sync::Arc::new(CeilingFn))
1966            .with_function(std::sync::Arc::new(FloorFn))
1967            .with_function(std::sync::Arc::new(CeilingMathFn))
1968            .with_function(std::sync::Arc::new(FloorMathFn));
1969        let ctx = interp(&wb);
1970        let c = ctx.context.get_function("", "CEILING").unwrap();
1971        let f = ctx.context.get_function("", "FLOOR").unwrap();
1972        let n = lit(LiteralValue::Number(5.1));
1973        let sig = lit(LiteralValue::Number(2.0));
1974        assert_eq!(
1975            c.dispatch(
1976                &[
1977                    ArgumentHandle::new(&n, &ctx),
1978                    ArgumentHandle::new(&sig, &ctx)
1979                ],
1980                &ctx.function_context(None)
1981            )
1982            .unwrap()
1983            .into_literal(),
1984            LiteralValue::Number(6.0)
1985        );
1986        assert_eq!(
1987            f.dispatch(
1988                &[
1989                    ArgumentHandle::new(&n, &ctx),
1990                    ArgumentHandle::new(&sig, &ctx)
1991                ],
1992                &ctx.function_context(None)
1993            )
1994            .unwrap()
1995            .into_literal(),
1996            LiteralValue::Number(4.0)
1997        );
1998    }
1999
2000    #[test]
2001    fn quotient_basic_and_div_zero() {
2002        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(QuotientFn));
2003        let ctx = interp(&wb);
2004        let f = ctx.context.get_function("", "QUOTIENT").unwrap();
2005
2006        let ten = lit(LiteralValue::Int(10));
2007        let three = lit(LiteralValue::Int(3));
2008        assert_eq!(
2009            f.dispatch(
2010                &[
2011                    ArgumentHandle::new(&ten, &ctx),
2012                    ArgumentHandle::new(&three, &ctx),
2013                ],
2014                &ctx.function_context(None),
2015            )
2016            .unwrap()
2017            .into_literal(),
2018            LiteralValue::Number(3.0)
2019        );
2020
2021        let neg_ten = lit(LiteralValue::Int(-10));
2022        assert_eq!(
2023            f.dispatch(
2024                &[
2025                    ArgumentHandle::new(&neg_ten, &ctx),
2026                    ArgumentHandle::new(&three, &ctx),
2027                ],
2028                &ctx.function_context(None),
2029            )
2030            .unwrap()
2031            .into_literal(),
2032            LiteralValue::Number(-3.0)
2033        );
2034
2035        let zero = lit(LiteralValue::Int(0));
2036        match f
2037            .dispatch(
2038                &[
2039                    ArgumentHandle::new(&ten, &ctx),
2040                    ArgumentHandle::new(&zero, &ctx),
2041                ],
2042                &ctx.function_context(None),
2043            )
2044            .unwrap()
2045            .into_literal()
2046        {
2047            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
2048            other => panic!("expected #DIV/0!, got {other:?}"),
2049        }
2050    }
2051
2052    #[test]
2053    fn even_odd_examples() {
2054        let wb = TestWorkbook::new()
2055            .with_function(std::sync::Arc::new(EvenFn))
2056            .with_function(std::sync::Arc::new(OddFn));
2057        let ctx = interp(&wb);
2058
2059        let even = ctx.context.get_function("", "EVEN").unwrap();
2060        let odd = ctx.context.get_function("", "ODD").unwrap();
2061
2062        let one_half = lit(LiteralValue::Number(1.5));
2063        let three = lit(LiteralValue::Int(3));
2064        let neg_one = lit(LiteralValue::Int(-1));
2065        let two = lit(LiteralValue::Int(2));
2066        let zero = lit(LiteralValue::Int(0));
2067
2068        assert_eq!(
2069            even.dispatch(
2070                &[ArgumentHandle::new(&one_half, &ctx)],
2071                &ctx.function_context(None),
2072            )
2073            .unwrap()
2074            .into_literal(),
2075            LiteralValue::Number(2.0)
2076        );
2077        assert_eq!(
2078            even.dispatch(
2079                &[ArgumentHandle::new(&three, &ctx)],
2080                &ctx.function_context(None),
2081            )
2082            .unwrap()
2083            .into_literal(),
2084            LiteralValue::Number(4.0)
2085        );
2086        assert_eq!(
2087            even.dispatch(
2088                &[ArgumentHandle::new(&neg_one, &ctx)],
2089                &ctx.function_context(None),
2090            )
2091            .unwrap()
2092            .into_literal(),
2093            LiteralValue::Number(-2.0)
2094        );
2095        assert_eq!(
2096            even.dispatch(
2097                &[ArgumentHandle::new(&two, &ctx)],
2098                &ctx.function_context(None),
2099            )
2100            .unwrap()
2101            .into_literal(),
2102            LiteralValue::Number(2.0)
2103        );
2104
2105        assert_eq!(
2106            odd.dispatch(
2107                &[ArgumentHandle::new(&one_half, &ctx)],
2108                &ctx.function_context(None),
2109            )
2110            .unwrap()
2111            .into_literal(),
2112            LiteralValue::Number(3.0)
2113        );
2114        assert_eq!(
2115            odd.dispatch(
2116                &[ArgumentHandle::new(&two, &ctx)],
2117                &ctx.function_context(None),
2118            )
2119            .unwrap()
2120            .into_literal(),
2121            LiteralValue::Number(3.0)
2122        );
2123        assert_eq!(
2124            odd.dispatch(
2125                &[ArgumentHandle::new(&neg_one, &ctx)],
2126                &ctx.function_context(None),
2127            )
2128            .unwrap()
2129            .into_literal(),
2130            LiteralValue::Number(-1.0)
2131        );
2132        assert_eq!(
2133            odd.dispatch(
2134                &[ArgumentHandle::new(&zero, &ctx)],
2135                &ctx.function_context(None),
2136            )
2137            .unwrap()
2138            .into_literal(),
2139            LiteralValue::Number(1.0)
2140        );
2141    }
2142
2143    #[test]
2144    fn sqrtpi_multinomial_and_seriessum_examples() {
2145        let wb = TestWorkbook::new()
2146            .with_function(std::sync::Arc::new(SqrtPiFn))
2147            .with_function(std::sync::Arc::new(MultinomialFn))
2148            .with_function(std::sync::Arc::new(SeriesSumFn));
2149        let ctx = interp(&wb);
2150
2151        let sqrtpi = ctx.context.get_function("", "SQRTPI").unwrap();
2152        let one = lit(LiteralValue::Int(1));
2153        match sqrtpi
2154            .dispatch(
2155                &[ArgumentHandle::new(&one, &ctx)],
2156                &ctx.function_context(None),
2157            )
2158            .unwrap()
2159            .into_literal()
2160        {
2161            LiteralValue::Number(v) => assert!((v - std::f64::consts::PI.sqrt()).abs() < 1e-12),
2162            other => panic!("expected numeric SQRTPI, got {other:?}"),
2163        }
2164
2165        let multinomial = ctx.context.get_function("", "MULTINOMIAL").unwrap();
2166        let two = lit(LiteralValue::Int(2));
2167        let three = lit(LiteralValue::Int(3));
2168        let four = lit(LiteralValue::Int(4));
2169        assert_eq!(
2170            multinomial
2171                .dispatch(
2172                    &[
2173                        ArgumentHandle::new(&two, &ctx),
2174                        ArgumentHandle::new(&three, &ctx),
2175                        ArgumentHandle::new(&four, &ctx),
2176                    ],
2177                    &ctx.function_context(None),
2178                )
2179                .unwrap()
2180                .into_literal(),
2181            LiteralValue::Number(1260.0)
2182        );
2183
2184        let seriessum = ctx.context.get_function("", "SERIESSUM").unwrap();
2185        let x = lit(LiteralValue::Int(2));
2186        let n0 = lit(LiteralValue::Int(0));
2187        let m1 = lit(LiteralValue::Int(1));
2188        let coeffs = ASTNode::new(
2189            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
2190                LiteralValue::Int(1),
2191                LiteralValue::Int(2),
2192                LiteralValue::Int(3),
2193            ]])),
2194            None,
2195        );
2196        assert_eq!(
2197            seriessum
2198                .dispatch(
2199                    &[
2200                        ArgumentHandle::new(&x, &ctx),
2201                        ArgumentHandle::new(&n0, &ctx),
2202                        ArgumentHandle::new(&m1, &ctx),
2203                        ArgumentHandle::new(&coeffs, &ctx),
2204                    ],
2205                    &ctx.function_context(None),
2206                )
2207                .unwrap()
2208                .into_literal(),
2209            LiteralValue::Number(17.0)
2210        );
2211    }
2212
2213    #[test]
2214    fn sumsq_basic() {
2215        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SumsqFn));
2216        let ctx = interp(&wb);
2217        let f = ctx.context.get_function("", "SUMSQ").unwrap();
2218        let a = lit(LiteralValue::Int(3));
2219        let b = lit(LiteralValue::Int(4));
2220        assert_eq!(
2221            f.dispatch(
2222                &[ArgumentHandle::new(&a, &ctx), ArgumentHandle::new(&b, &ctx)],
2223                &ctx.function_context(None)
2224            )
2225            .unwrap()
2226            .into_literal(),
2227            LiteralValue::Number(25.0)
2228        );
2229    }
2230
2231    #[test]
2232    fn mround_sign_and_midpoint() {
2233        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MroundFn));
2234        let ctx = interp(&wb);
2235        let f = ctx.context.get_function("", "MROUND").unwrap();
2236
2237        let n = lit(LiteralValue::Number(1.3));
2238        let m = lit(LiteralValue::Number(0.2));
2239        match f
2240            .dispatch(
2241                &[ArgumentHandle::new(&n, &ctx), ArgumentHandle::new(&m, &ctx)],
2242                &ctx.function_context(None),
2243            )
2244            .unwrap()
2245            .into_literal()
2246        {
2247            LiteralValue::Number(v) => assert!((v - 1.4).abs() < 1e-12),
2248            other => panic!("expected numeric result, got {other:?}"),
2249        }
2250
2251        let bad_m = lit(LiteralValue::Number(-2.0));
2252        let five = lit(LiteralValue::Number(5.0));
2253        match f
2254            .dispatch(
2255                &[
2256                    ArgumentHandle::new(&five, &ctx),
2257                    ArgumentHandle::new(&bad_m, &ctx),
2258                ],
2259                &ctx.function_context(None),
2260            )
2261            .unwrap()
2262            .into_literal()
2263        {
2264            LiteralValue::Error(e) => assert_eq!(e, "#NUM!"),
2265            other => panic!("expected #NUM!, got {other:?}"),
2266        }
2267    }
2268
2269    #[test]
2270    fn roman_and_arabic_examples() {
2271        let wb = TestWorkbook::new()
2272            .with_function(std::sync::Arc::new(RomanFn))
2273            .with_function(std::sync::Arc::new(ArabicFn));
2274        let ctx = interp(&wb);
2275
2276        let roman = ctx.context.get_function("", "ROMAN").unwrap();
2277        let n499 = lit(LiteralValue::Int(499));
2278        let out = roman
2279            .dispatch(
2280                &[ArgumentHandle::new(&n499, &ctx)],
2281                &ctx.function_context(None),
2282            )
2283            .unwrap()
2284            .into_literal();
2285        assert_eq!(out, LiteralValue::Text("CDXCIX".to_string()));
2286
2287        let form4 = lit(LiteralValue::Int(4));
2288        let out_form4 = roman
2289            .dispatch(
2290                &[
2291                    ArgumentHandle::new(&n499, &ctx),
2292                    ArgumentHandle::new(&form4, &ctx),
2293                ],
2294                &ctx.function_context(None),
2295            )
2296            .unwrap()
2297            .into_literal();
2298        assert_eq!(out_form4, LiteralValue::Text("ID".to_string()));
2299
2300        let arabic = ctx.context.get_function("", "ARABIC").unwrap();
2301        let roman_text = lit(LiteralValue::Text("CDXCIX".to_string()));
2302        let out_arabic = arabic
2303            .dispatch(
2304                &[ArgumentHandle::new(&roman_text, &ctx)],
2305                &ctx.function_context(None),
2306            )
2307            .unwrap()
2308            .into_literal();
2309        assert_eq!(out_arabic, LiteralValue::Number(499.0));
2310    }
2311}