Skip to main content

formualizer_eval/builtins/math/
trig.rs

1use super::super::utils::{
2    ARG_ANY_ONE, ARG_NUM_LENIENT_ONE, ARG_NUM_LENIENT_TWO, EPSILON_NEAR_ZERO,
3    binary_numeric_elementwise, unary_numeric_arg, unary_numeric_elementwise,
4};
5use crate::args::ArgSchema;
6use crate::function::Function;
7use crate::traits::{ArgumentHandle, FunctionContext};
8use formualizer_common::{ExcelError, LiteralValue};
9use formualizer_macros::func_caps;
10use std::f64::consts::PI;
11
12/* ─────────────────────────── TRIG: circular ────────────────────────── */
13
14#[derive(Debug)]
15pub struct SinFn;
16impl Function for SinFn {
17    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
18    fn name(&self) -> &'static str {
19        "SIN"
20    }
21    fn min_args(&self) -> usize {
22        1
23    }
24    fn arg_schema(&self) -> &'static [ArgSchema] {
25        &ARG_NUM_LENIENT_ONE[..]
26    }
27    fn eval<'a, 'b, 'c>(
28        &self,
29        args: &'c [ArgumentHandle<'a, 'b>],
30        ctx: &dyn FunctionContext<'b>,
31    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
32        unary_numeric_elementwise(args, ctx, |x| Ok(LiteralValue::Number(x.sin())))
33    }
34}
35
36#[cfg(test)]
37mod tests_sin {
38    use super::*;
39    use crate::test_workbook::TestWorkbook;
40    use crate::traits::ArgumentHandle;
41    use formualizer_parse::LiteralValue;
42
43    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
44        wb.interpreter()
45    }
46    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
47        formualizer_parse::parser::ASTNode::new(
48            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
49            None,
50        )
51    }
52    fn assert_close(a: f64, b: f64) {
53        assert!((a - b).abs() < 1e-9, "{a} !~= {b}");
54    }
55
56    #[test]
57    fn test_sin_basic() {
58        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SinFn));
59        let ctx = interp(&wb);
60        let sin = ctx.context.get_function("", "SIN").unwrap();
61        let a0 = make_num_ast(PI / 2.0);
62        let args = vec![ArgumentHandle::new(&a0, &ctx)];
63        match sin
64            .dispatch(&args, &ctx.function_context(None))
65            .unwrap()
66            .into_literal()
67        {
68            LiteralValue::Number(n) => assert_close(n, 1.0),
69            v => panic!("unexpected {v:?}"),
70        }
71    }
72
73    #[test]
74    fn test_sin_array_literal() {
75        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SinFn));
76        let ctx = interp(&wb);
77        let sin = ctx.context.get_function("", "SIN").unwrap();
78        let arr = formualizer_parse::parser::ASTNode::new(
79            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Array(vec![vec![
80                LiteralValue::Number(0.0),
81                LiteralValue::Number(PI / 2.0),
82            ]])),
83            None,
84        );
85        let args = vec![ArgumentHandle::new(&arr, &ctx)];
86
87        match sin
88            .dispatch(&args, &ctx.function_context(None))
89            .unwrap()
90            .into_literal()
91        {
92            formualizer_common::LiteralValue::Array(rows) => {
93                assert_eq!(rows.len(), 1);
94                assert_eq!(rows[0].len(), 2);
95                match (&rows[0][0], &rows[0][1]) {
96                    (
97                        formualizer_common::LiteralValue::Number(a),
98                        formualizer_common::LiteralValue::Number(b),
99                    ) => {
100                        assert_close(*a, 0.0);
101                        assert_close(*b, 1.0);
102                    }
103                    other => panic!("unexpected {other:?}"),
104                }
105            }
106            other => panic!("expected array, got {other:?}"),
107        }
108    }
109}
110
111#[derive(Debug)]
112pub struct CosFn;
113impl Function for CosFn {
114    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
115    fn name(&self) -> &'static str {
116        "COS"
117    }
118    fn min_args(&self) -> usize {
119        1
120    }
121    fn arg_schema(&self) -> &'static [ArgSchema] {
122        &ARG_NUM_LENIENT_ONE[..]
123    }
124    fn eval<'a, 'b, 'c>(
125        &self,
126        args: &'c [ArgumentHandle<'a, 'b>],
127        ctx: &dyn FunctionContext<'b>,
128    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
129        unary_numeric_elementwise(args, ctx, |x| Ok(LiteralValue::Number(x.cos())))
130    }
131}
132
133#[cfg(test)]
134mod tests_cos {
135    use super::*;
136    use crate::test_workbook::TestWorkbook;
137    use crate::traits::ArgumentHandle;
138    use formualizer_parse::LiteralValue;
139    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
140        wb.interpreter()
141    }
142    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
143        formualizer_parse::parser::ASTNode::new(
144            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
145            None,
146        )
147    }
148    fn assert_close(a: f64, b: f64) {
149        assert!((a - b).abs() < 1e-9);
150    }
151    #[test]
152    fn test_cos_basic() {
153        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CosFn));
154        let ctx = interp(&wb);
155        let cos = ctx.context.get_function("", "COS").unwrap();
156        let a0 = make_num_ast(0.0);
157        let args = vec![ArgumentHandle::new(&a0, &ctx)];
158        match cos
159            .dispatch(&args, &ctx.function_context(None))
160            .unwrap()
161            .into_literal()
162        {
163            LiteralValue::Number(n) => assert_close(n, 1.0),
164            v => panic!("unexpected {v:?}"),
165        }
166    }
167}
168
169#[derive(Debug)]
170pub struct TanFn;
171impl Function for TanFn {
172    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
173    fn name(&self) -> &'static str {
174        "TAN"
175    }
176    fn min_args(&self) -> usize {
177        1
178    }
179    fn arg_schema(&self) -> &'static [ArgSchema] {
180        &ARG_NUM_LENIENT_ONE[..]
181    }
182    fn eval<'a, 'b, 'c>(
183        &self,
184        args: &'c [ArgumentHandle<'a, 'b>],
185        ctx: &dyn FunctionContext<'b>,
186    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
187        unary_numeric_elementwise(args, ctx, |x| Ok(LiteralValue::Number(x.tan())))
188    }
189}
190
191#[cfg(test)]
192mod tests_tan {
193    use super::*;
194    use crate::test_workbook::TestWorkbook;
195    use crate::traits::ArgumentHandle;
196    use formualizer_parse::LiteralValue;
197    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
198        wb.interpreter()
199    }
200    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
201        formualizer_parse::parser::ASTNode::new(
202            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
203            None,
204        )
205    }
206    fn assert_close(a: f64, b: f64) {
207        assert!((a - b).abs() < 1e-9);
208    }
209    #[test]
210    fn test_tan_basic() {
211        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TanFn));
212        let ctx = interp(&wb);
213        let tan = ctx.context.get_function("", "TAN").unwrap();
214        let a0 = make_num_ast(PI / 4.0);
215        let args = vec![ArgumentHandle::new(&a0, &ctx)];
216        match tan
217            .dispatch(&args, &ctx.function_context(None))
218            .unwrap()
219            .into_literal()
220        {
221            LiteralValue::Number(n) => assert_close(n, 1.0),
222            v => panic!("unexpected {v:?}"),
223        }
224    }
225}
226
227#[derive(Debug)]
228pub struct AsinFn;
229impl Function for AsinFn {
230    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
231    fn name(&self) -> &'static str {
232        "ASIN"
233    }
234    fn min_args(&self) -> usize {
235        1
236    }
237    fn arg_schema(&self) -> &'static [ArgSchema] {
238        &ARG_NUM_LENIENT_ONE[..]
239    }
240    fn eval<'a, 'b, 'c>(
241        &self,
242        args: &'c [ArgumentHandle<'a, 'b>],
243        _ctx: &dyn FunctionContext<'b>,
244    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
245        let x = unary_numeric_arg(args)?;
246        if !(-1.0..=1.0).contains(&x) {
247            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
248                ExcelError::new_num(),
249            )));
250        }
251        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
252            x.asin(),
253        )))
254    }
255}
256
257#[cfg(test)]
258mod tests_asin {
259    use super::*;
260    use crate::test_workbook::TestWorkbook;
261    use crate::traits::ArgumentHandle;
262    use formualizer_parse::LiteralValue;
263    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
264        wb.interpreter()
265    }
266    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
267        formualizer_parse::parser::ASTNode::new(
268            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
269            None,
270        )
271    }
272    fn assert_close(a: f64, b: f64) {
273        assert!((a - b).abs() < 1e-9);
274    }
275    #[test]
276    fn test_asin_basic_and_domain() {
277        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AsinFn));
278        let ctx = interp(&wb);
279        let asin = ctx.context.get_function("", "ASIN").unwrap();
280        // valid
281        let a0 = make_num_ast(0.5);
282        let args = vec![ArgumentHandle::new(&a0, &ctx)];
283        match asin
284            .dispatch(&args, &ctx.function_context(None))
285            .unwrap()
286            .into_literal()
287        {
288            LiteralValue::Number(n) => assert_close(n, (0.5f64).asin()),
289            v => panic!("unexpected {v:?}"),
290        }
291        // invalid domain
292        let a1 = make_num_ast(2.0);
293        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
294        match asin
295            .dispatch(&args2, &ctx.function_context(None))
296            .unwrap()
297            .into_literal()
298        {
299            LiteralValue::Error(e) => assert_eq!(e, "#NUM!"),
300            v => panic!("expected error, got {v:?}"),
301        }
302    }
303}
304
305#[derive(Debug)]
306pub struct AcosFn;
307impl Function for AcosFn {
308    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
309    fn name(&self) -> &'static str {
310        "ACOS"
311    }
312    fn min_args(&self) -> usize {
313        1
314    }
315    fn arg_schema(&self) -> &'static [ArgSchema] {
316        &ARG_NUM_LENIENT_ONE[..]
317    }
318    fn eval<'a, 'b, 'c>(
319        &self,
320        args: &'c [ArgumentHandle<'a, 'b>],
321        _ctx: &dyn FunctionContext<'b>,
322    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
323        let x = unary_numeric_arg(args)?;
324        if !(-1.0..=1.0).contains(&x) {
325            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
326                ExcelError::new_num(),
327            )));
328        }
329        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
330            x.acos(),
331        )))
332    }
333}
334
335#[cfg(test)]
336mod tests_acos {
337    use super::*;
338    use crate::test_workbook::TestWorkbook;
339    use crate::traits::ArgumentHandle;
340    use formualizer_parse::LiteralValue;
341    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
342        wb.interpreter()
343    }
344    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
345        formualizer_parse::parser::ASTNode::new(
346            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
347            None,
348        )
349    }
350    fn assert_close(a: f64, b: f64) {
351        assert!((a - b).abs() < 1e-9);
352    }
353    #[test]
354    fn test_acos_basic_and_domain() {
355        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AcosFn));
356        let ctx = interp(&wb);
357        let acos = ctx.context.get_function("", "ACOS").unwrap();
358        let a0 = make_num_ast(0.5);
359        let args = vec![ArgumentHandle::new(&a0, &ctx)];
360        match acos
361            .dispatch(&args, &ctx.function_context(None))
362            .unwrap()
363            .into_literal()
364        {
365            LiteralValue::Number(n) => assert_close(n, (0.5f64).acos()),
366            v => panic!("unexpected {v:?}"),
367        }
368        let a1 = make_num_ast(-2.0);
369        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
370        match acos
371            .dispatch(&args2, &ctx.function_context(None))
372            .unwrap()
373            .into_literal()
374        {
375            LiteralValue::Error(e) => assert_eq!(e, "#NUM!"),
376            v => panic!("expected error, got {v:?}"),
377        }
378    }
379}
380
381#[derive(Debug)]
382pub struct AtanFn;
383impl Function for AtanFn {
384    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
385    fn name(&self) -> &'static str {
386        "ATAN"
387    }
388    fn min_args(&self) -> usize {
389        1
390    }
391    fn arg_schema(&self) -> &'static [ArgSchema] {
392        &ARG_NUM_LENIENT_ONE[..]
393    }
394    fn eval<'a, 'b, 'c>(
395        &self,
396        args: &'c [ArgumentHandle<'a, 'b>],
397        _ctx: &dyn FunctionContext<'b>,
398    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
399        let x = unary_numeric_arg(args)?;
400        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
401            x.atan(),
402        )))
403    }
404}
405
406#[cfg(test)]
407mod tests_atan {
408    use super::*;
409    use crate::test_workbook::TestWorkbook;
410    use crate::traits::ArgumentHandle;
411    use formualizer_parse::LiteralValue;
412    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
413        wb.interpreter()
414    }
415    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
416        formualizer_parse::parser::ASTNode::new(
417            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
418            None,
419        )
420    }
421    fn assert_close(a: f64, b: f64) {
422        assert!((a - b).abs() < 1e-9);
423    }
424    #[test]
425    fn test_atan_basic() {
426        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AtanFn));
427        let ctx = interp(&wb);
428        let atan = ctx.context.get_function("", "ATAN").unwrap();
429        let a0 = make_num_ast(1.0);
430        let args = vec![ArgumentHandle::new(&a0, &ctx)];
431        match atan
432            .dispatch(&args, &ctx.function_context(None))
433            .unwrap()
434            .into_literal()
435        {
436            LiteralValue::Number(n) => assert_close(n, (1.0f64).atan()),
437            v => panic!("unexpected {v:?}"),
438        }
439    }
440}
441
442#[derive(Debug)]
443pub struct Atan2Fn;
444impl Function for Atan2Fn {
445    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
446    fn name(&self) -> &'static str {
447        "ATAN2"
448    }
449    fn min_args(&self) -> usize {
450        2
451    }
452    fn arg_schema(&self) -> &'static [ArgSchema] {
453        &ARG_NUM_LENIENT_TWO[..]
454    }
455    fn eval<'a, 'b, 'c>(
456        &self,
457        args: &'c [ArgumentHandle<'a, 'b>],
458        ctx: &dyn FunctionContext<'b>,
459    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
460        // Excel: ATAN2(x_num, y_num)
461        binary_numeric_elementwise(args, ctx, |x, y| {
462            if x == 0.0 && y == 0.0 {
463                Ok(LiteralValue::Error(ExcelError::from_error_string(
464                    "#DIV/0!",
465                )))
466            } else {
467                Ok(LiteralValue::Number(y.atan2(x)))
468            }
469        })
470    }
471}
472
473#[cfg(test)]
474mod tests_atan2 {
475    use super::*;
476    use crate::test_workbook::TestWorkbook;
477    use crate::traits::ArgumentHandle;
478    use formualizer_parse::LiteralValue;
479    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
480        wb.interpreter()
481    }
482    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
483        formualizer_parse::parser::ASTNode::new(
484            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
485            None,
486        )
487    }
488    fn assert_close(a: f64, b: f64) {
489        assert!((a - b).abs() < 1e-9);
490    }
491    #[test]
492    fn test_atan2_basic_and_zero_zero() {
493        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(Atan2Fn));
494        let ctx = interp(&wb);
495        let atan2 = ctx.context.get_function("", "ATAN2").unwrap();
496        // ATAN2(1,1) = pi/4
497        let a0 = make_num_ast(1.0);
498        let a1 = make_num_ast(1.0);
499        let args = vec![
500            ArgumentHandle::new(&a0, &ctx),
501            ArgumentHandle::new(&a1, &ctx),
502        ];
503        match atan2
504            .dispatch(&args, &ctx.function_context(None))
505            .unwrap()
506            .into_literal()
507        {
508            LiteralValue::Number(n) => assert_close(n, PI / 4.0),
509            v => panic!("unexpected {v:?}"),
510        }
511        // ATAN2(0,0) => #DIV/0!
512        let b0 = make_num_ast(0.0);
513        let b1 = make_num_ast(0.0);
514        let args2 = vec![
515            ArgumentHandle::new(&b0, &ctx),
516            ArgumentHandle::new(&b1, &ctx),
517        ];
518        match atan2
519            .dispatch(&args2, &ctx.function_context(None))
520            .unwrap()
521            .into_literal()
522        {
523            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
524            v => panic!("expected error, got {v:?}"),
525        }
526    }
527
528    #[test]
529    fn test_atan2_broadcast_scalar_over_array() {
530        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(Atan2Fn));
531        let ctx = interp(&wb);
532        let atan2 = ctx.context.get_function("", "ATAN2").unwrap();
533
534        // ATAN2(x_num=1, y_num={0,1}) => {0, pi/4}
535        let x = make_num_ast(1.0);
536        let y = formualizer_parse::parser::ASTNode::new(
537            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Array(vec![vec![
538                LiteralValue::Number(0.0),
539                LiteralValue::Number(1.0),
540            ]])),
541            None,
542        );
543        let args = vec![ArgumentHandle::new(&x, &ctx), ArgumentHandle::new(&y, &ctx)];
544
545        match atan2
546            .dispatch(&args, &ctx.function_context(None))
547            .unwrap()
548            .into_literal()
549        {
550            formualizer_common::LiteralValue::Array(rows) => {
551                assert_eq!(rows.len(), 1);
552                assert_eq!(rows[0].len(), 2);
553                match (&rows[0][0], &rows[0][1]) {
554                    (
555                        formualizer_common::LiteralValue::Number(a),
556                        formualizer_common::LiteralValue::Number(b),
557                    ) => {
558                        assert_close(*a, 0.0);
559                        assert_close(*b, PI / 4.0);
560                    }
561                    other => panic!("unexpected {other:?}"),
562                }
563            }
564            other => panic!("expected array, got {other:?}"),
565        }
566    }
567}
568
569#[derive(Debug)]
570pub struct SecFn;
571impl Function for SecFn {
572    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
573    fn name(&self) -> &'static str {
574        "SEC"
575    }
576    fn min_args(&self) -> usize {
577        1
578    }
579    fn arg_schema(&self) -> &'static [ArgSchema] {
580        &ARG_NUM_LENIENT_ONE[..]
581    }
582    fn eval<'a, 'b, 'c>(
583        &self,
584        args: &'c [ArgumentHandle<'a, 'b>],
585        _ctx: &dyn FunctionContext<'b>,
586    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
587        let x = unary_numeric_arg(args)?;
588        let c = x.cos();
589        if c.abs() < EPSILON_NEAR_ZERO {
590            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
591                ExcelError::from_error_string("#DIV/0!"),
592            )));
593        }
594        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
595            1.0 / c,
596        )))
597    }
598}
599
600#[cfg(test)]
601mod tests_sec {
602    use super::*;
603    use crate::test_workbook::TestWorkbook;
604    use crate::traits::ArgumentHandle;
605    use formualizer_parse::LiteralValue;
606    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
607        wb.interpreter()
608    }
609    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
610        formualizer_parse::parser::ASTNode::new(
611            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
612            None,
613        )
614    }
615    fn assert_close(a: f64, b: f64) {
616        assert!((a - b).abs() < 1e-9);
617    }
618    #[test]
619    fn test_sec_basic_and_div0() {
620        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SecFn));
621        let ctx = interp(&wb);
622        let sec = ctx.context.get_function("", "SEC").unwrap();
623        let a0 = make_num_ast(0.0);
624        let args = vec![ArgumentHandle::new(&a0, &ctx)];
625        match sec
626            .dispatch(&args, &ctx.function_context(None))
627            .unwrap()
628            .into_literal()
629        {
630            LiteralValue::Number(n) => assert_close(n, 1.0),
631            v => panic!("unexpected {v:?}"),
632        }
633        let a1 = make_num_ast(PI / 2.0);
634        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
635        match sec
636            .dispatch(&args2, &ctx.function_context(None))
637            .unwrap()
638            .into_literal()
639        {
640            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
641            LiteralValue::Number(n) => assert!(n.abs() > 1e12), // near singularity
642            v => panic!("unexpected {v:?}"),
643        }
644    }
645}
646
647#[derive(Debug)]
648pub struct CscFn;
649impl Function for CscFn {
650    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
651    fn name(&self) -> &'static str {
652        "CSC"
653    }
654    fn min_args(&self) -> usize {
655        1
656    }
657    fn arg_schema(&self) -> &'static [ArgSchema] {
658        &ARG_NUM_LENIENT_ONE[..]
659    }
660    fn eval<'a, 'b, 'c>(
661        &self,
662        args: &'c [ArgumentHandle<'a, 'b>],
663        _ctx: &dyn FunctionContext<'b>,
664    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
665        let x = unary_numeric_arg(args)?;
666        let s = x.sin();
667        if s.abs() < EPSILON_NEAR_ZERO {
668            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
669                ExcelError::from_error_string("#DIV/0!"),
670            )));
671        }
672        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
673            1.0 / s,
674        )))
675    }
676}
677
678#[cfg(test)]
679mod tests_csc {
680    use super::*;
681    use crate::test_workbook::TestWorkbook;
682    use crate::traits::ArgumentHandle;
683    use formualizer_parse::LiteralValue;
684    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
685        wb.interpreter()
686    }
687    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
688        formualizer_parse::parser::ASTNode::new(
689            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
690            None,
691        )
692    }
693    fn assert_close(a: f64, b: f64) {
694        assert!((a - b).abs() < 1e-9);
695    }
696    #[test]
697    fn test_csc_basic_and_div0() {
698        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CscFn));
699        let ctx = interp(&wb);
700        let csc = ctx.context.get_function("", "CSC").unwrap();
701        let a0 = make_num_ast(PI / 2.0);
702        let args = vec![ArgumentHandle::new(&a0, &ctx)];
703        match csc
704            .dispatch(&args, &ctx.function_context(None))
705            .unwrap()
706            .into_literal()
707        {
708            LiteralValue::Number(n) => assert_close(n, 1.0),
709            v => panic!("unexpected {v:?}"),
710        }
711        let a1 = make_num_ast(0.0);
712        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
713        match csc
714            .dispatch(&args2, &ctx.function_context(None))
715            .unwrap()
716            .into_literal()
717        {
718            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
719            v => panic!("expected error, got {v:?}"),
720        }
721    }
722}
723
724#[derive(Debug)]
725pub struct CotFn;
726impl Function for CotFn {
727    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
728    fn name(&self) -> &'static str {
729        "COT"
730    }
731    fn min_args(&self) -> usize {
732        1
733    }
734    fn arg_schema(&self) -> &'static [ArgSchema] {
735        &ARG_NUM_LENIENT_ONE[..]
736    }
737    fn eval<'a, 'b, 'c>(
738        &self,
739        args: &'c [ArgumentHandle<'a, 'b>],
740        _ctx: &dyn FunctionContext<'b>,
741    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
742        let x = unary_numeric_arg(args)?;
743        let t = x.tan();
744        if t.abs() < EPSILON_NEAR_ZERO {
745            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
746                ExcelError::from_error_string("#DIV/0!"),
747            )));
748        }
749        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
750            1.0 / t,
751        )))
752    }
753}
754
755#[cfg(test)]
756mod tests_cot {
757    use super::*;
758    use crate::test_workbook::TestWorkbook;
759    use crate::traits::ArgumentHandle;
760    use formualizer_parse::LiteralValue;
761    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
762        wb.interpreter()
763    }
764    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
765        formualizer_parse::parser::ASTNode::new(
766            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
767            None,
768        )
769    }
770    fn assert_close(a: f64, b: f64) {
771        assert!((a - b).abs() < 1e-9);
772    }
773    #[test]
774    fn test_cot_basic_and_div0() {
775        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CotFn));
776        let ctx = interp(&wb);
777        let cot = ctx.context.get_function("", "COT").unwrap();
778        let a0 = make_num_ast(PI / 4.0);
779        let args = vec![ArgumentHandle::new(&a0, &ctx)];
780        match cot
781            .dispatch(&args, &ctx.function_context(None))
782            .unwrap()
783            .into_literal()
784        {
785            LiteralValue::Number(n) => assert_close(n, 1.0),
786            v => panic!("unexpected {v:?}"),
787        }
788        let a1 = make_num_ast(0.0);
789        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
790        match cot
791            .dispatch(&args2, &ctx.function_context(None))
792            .unwrap()
793            .into_literal()
794        {
795            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
796            v => panic!("expected error, got {v:?}"),
797        }
798    }
799}
800
801#[derive(Debug)]
802pub struct AcotFn;
803impl Function for AcotFn {
804    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
805    fn name(&self) -> &'static str {
806        "ACOT"
807    }
808    fn min_args(&self) -> usize {
809        1
810    }
811    fn arg_schema(&self) -> &'static [ArgSchema] {
812        &ARG_NUM_LENIENT_ONE[..]
813    }
814    fn eval<'a, 'b, 'c>(
815        &self,
816        args: &'c [ArgumentHandle<'a, 'b>],
817        _ctx: &dyn FunctionContext<'b>,
818    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
819        let x = unary_numeric_arg(args)?;
820        let result = if x == 0.0 {
821            PI / 2.0
822        } else if x > 0.0 {
823            (1.0 / x).atan()
824        } else {
825            (1.0 / x).atan() + PI
826        };
827        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
828            result,
829        )))
830    }
831}
832
833#[cfg(test)]
834mod tests_acot {
835    use super::*;
836    use crate::test_workbook::TestWorkbook;
837    use crate::traits::ArgumentHandle;
838    use formualizer_parse::LiteralValue;
839    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
840        wb.interpreter()
841    }
842    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
843        formualizer_parse::parser::ASTNode::new(
844            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
845            None,
846        )
847    }
848    fn assert_close(a: f64, b: f64) {
849        assert!((a - b).abs() < 1e-9);
850    }
851    #[test]
852    fn test_acot_basic() {
853        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AcotFn));
854        let ctx = interp(&wb);
855        let acot = ctx.context.get_function("", "ACOT").unwrap();
856        let a0 = make_num_ast(2.0);
857        let args = vec![ArgumentHandle::new(&a0, &ctx)];
858        match acot
859            .dispatch(&args, &ctx.function_context(None))
860            .unwrap()
861            .into_literal()
862        {
863            LiteralValue::Number(n) => assert_close(n, 0.4636476090008061),
864            v => panic!("unexpected {v:?}"),
865        }
866    }
867}
868
869/* ─────────────────────────── TRIG: hyperbolic ──────────────────────── */
870
871#[derive(Debug)]
872pub struct SinhFn;
873impl Function for SinhFn {
874    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
875    fn name(&self) -> &'static str {
876        "SINH"
877    }
878    fn min_args(&self) -> usize {
879        1
880    }
881    fn arg_schema(&self) -> &'static [ArgSchema] {
882        &ARG_NUM_LENIENT_ONE[..]
883    }
884    fn eval<'a, 'b, 'c>(
885        &self,
886        args: &'c [ArgumentHandle<'a, 'b>],
887        _ctx: &dyn FunctionContext<'b>,
888    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
889        let x = unary_numeric_arg(args)?;
890        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
891            x.sinh(),
892        )))
893    }
894}
895
896#[cfg(test)]
897mod tests_sinh {
898    use super::*;
899    use crate::test_workbook::TestWorkbook;
900    use crate::traits::ArgumentHandle;
901    use formualizer_parse::LiteralValue;
902    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
903        wb.interpreter()
904    }
905    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
906        formualizer_parse::parser::ASTNode::new(
907            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
908            None,
909        )
910    }
911    fn assert_close(a: f64, b: f64) {
912        assert!((a - b).abs() < 1e-9);
913    }
914    #[test]
915    fn test_sinh_basic() {
916        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SinhFn));
917        let ctx = interp(&wb);
918        let f = ctx.context.get_function("", "SINH").unwrap();
919        let a0 = make_num_ast(1.0);
920        let args = vec![ArgumentHandle::new(&a0, &ctx)];
921        let fctx = ctx.function_context(None);
922        match f.dispatch(&args, &fctx).unwrap().into_literal() {
923            LiteralValue::Number(n) => assert_close(n, (1.0f64).sinh()),
924            v => panic!("unexpected {v:?}"),
925        }
926    }
927}
928
929#[derive(Debug)]
930pub struct CoshFn;
931impl Function for CoshFn {
932    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
933    fn name(&self) -> &'static str {
934        "COSH"
935    }
936    fn min_args(&self) -> usize {
937        1
938    }
939    fn arg_schema(&self) -> &'static [ArgSchema] {
940        &ARG_NUM_LENIENT_ONE[..]
941    }
942    fn eval<'a, 'b, 'c>(
943        &self,
944        args: &'c [ArgumentHandle<'a, 'b>],
945        _ctx: &dyn FunctionContext<'b>,
946    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
947        let x = unary_numeric_arg(args)?;
948        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
949            x.cosh(),
950        )))
951    }
952}
953
954#[cfg(test)]
955mod tests_cosh {
956    use super::*;
957    use crate::test_workbook::TestWorkbook;
958    use crate::traits::ArgumentHandle;
959    use formualizer_parse::LiteralValue;
960    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
961        wb.interpreter()
962    }
963    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
964        formualizer_parse::parser::ASTNode::new(
965            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
966            None,
967        )
968    }
969    fn assert_close(a: f64, b: f64) {
970        assert!((a - b).abs() < 1e-9);
971    }
972    #[test]
973    fn test_cosh_basic() {
974        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CoshFn));
975        let ctx = interp(&wb);
976        let f = ctx.context.get_function("", "COSH").unwrap();
977        let a0 = make_num_ast(1.0);
978        let args = vec![ArgumentHandle::new(&a0, &ctx)];
979        match f
980            .dispatch(&args, &ctx.function_context(None))
981            .unwrap()
982            .into_literal()
983        {
984            LiteralValue::Number(n) => assert_close(n, (1.0f64).cosh()),
985            v => panic!("unexpected {v:?}"),
986        }
987    }
988}
989
990#[derive(Debug)]
991pub struct TanhFn;
992impl Function for TanhFn {
993    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
994    fn name(&self) -> &'static str {
995        "TANH"
996    }
997    fn min_args(&self) -> usize {
998        1
999    }
1000    fn arg_schema(&self) -> &'static [ArgSchema] {
1001        &ARG_NUM_LENIENT_ONE[..]
1002    }
1003    fn eval<'a, 'b, 'c>(
1004        &self,
1005        args: &'c [ArgumentHandle<'a, 'b>],
1006        _ctx: &dyn FunctionContext<'b>,
1007    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1008        let x = unary_numeric_arg(args)?;
1009        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1010            x.tanh(),
1011        )))
1012    }
1013}
1014
1015#[cfg(test)]
1016mod tests_tanh {
1017    use super::*;
1018    use crate::test_workbook::TestWorkbook;
1019    use crate::traits::ArgumentHandle;
1020    use formualizer_parse::LiteralValue;
1021    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1022        wb.interpreter()
1023    }
1024    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1025        formualizer_parse::parser::ASTNode::new(
1026            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1027            None,
1028        )
1029    }
1030    fn assert_close(a: f64, b: f64) {
1031        assert!((a - b).abs() < 1e-9);
1032    }
1033    #[test]
1034    fn test_tanh_basic() {
1035        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(TanhFn));
1036        let ctx = interp(&wb);
1037        let f = ctx.context.get_function("", "TANH").unwrap();
1038        let a0 = make_num_ast(0.5);
1039        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1040        match f
1041            .dispatch(&args, &ctx.function_context(None))
1042            .unwrap()
1043            .into_literal()
1044        {
1045            LiteralValue::Number(n) => assert_close(n, (0.5f64).tanh()),
1046            v => panic!("unexpected {v:?}"),
1047        }
1048    }
1049}
1050
1051#[derive(Debug)]
1052pub struct AsinhFn;
1053impl Function for AsinhFn {
1054    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1055    fn name(&self) -> &'static str {
1056        "ASINH"
1057    }
1058    fn min_args(&self) -> usize {
1059        1
1060    }
1061    fn arg_schema(&self) -> &'static [ArgSchema] {
1062        &ARG_NUM_LENIENT_ONE[..]
1063    }
1064    fn eval<'a, 'b, 'c>(
1065        &self,
1066        args: &'c [ArgumentHandle<'a, 'b>],
1067        _ctx: &dyn FunctionContext<'b>,
1068    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1069        let x = unary_numeric_arg(args)?;
1070        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1071            x.asinh(),
1072        )))
1073    }
1074}
1075
1076#[cfg(test)]
1077mod tests_asinh {
1078    use super::*;
1079    use crate::test_workbook::TestWorkbook;
1080    use crate::traits::ArgumentHandle;
1081    use formualizer_parse::LiteralValue;
1082    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1083        wb.interpreter()
1084    }
1085    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1086        formualizer_parse::parser::ASTNode::new(
1087            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1088            None,
1089        )
1090    }
1091    fn assert_close(a: f64, b: f64) {
1092        assert!((a - b).abs() < 1e-9);
1093    }
1094    #[test]
1095    fn test_asinh_basic() {
1096        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AsinhFn));
1097        let ctx = interp(&wb);
1098        let f = ctx.context.get_function("", "ASINH").unwrap();
1099        let a0 = make_num_ast(1.5);
1100        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1101        match f
1102            .dispatch(&args, &ctx.function_context(None))
1103            .unwrap()
1104            .into_literal()
1105        {
1106            LiteralValue::Number(n) => assert_close(n, (1.5f64).asinh()),
1107            v => panic!("unexpected {v:?}"),
1108        }
1109    }
1110}
1111
1112#[derive(Debug)]
1113pub struct AcoshFn;
1114impl Function for AcoshFn {
1115    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1116    fn name(&self) -> &'static str {
1117        "ACOSH"
1118    }
1119    fn min_args(&self) -> usize {
1120        1
1121    }
1122    fn arg_schema(&self) -> &'static [ArgSchema] {
1123        &ARG_ANY_ONE[..]
1124    }
1125    fn eval<'a, 'b, 'c>(
1126        &self,
1127        args: &'c [ArgumentHandle<'a, 'b>],
1128        _ctx: &dyn FunctionContext<'b>,
1129    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1130        let x = unary_numeric_arg(args)?;
1131        if x < 1.0 {
1132            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1133                ExcelError::new_num(),
1134            )));
1135        }
1136        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1137            x.acosh(),
1138        )))
1139    }
1140}
1141
1142#[cfg(test)]
1143mod tests_acosh {
1144    use super::*;
1145    use crate::test_workbook::TestWorkbook;
1146    use crate::traits::ArgumentHandle;
1147    use formualizer_parse::LiteralValue;
1148    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1149        wb.interpreter()
1150    }
1151    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1152        formualizer_parse::parser::ASTNode::new(
1153            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1154            None,
1155        )
1156    }
1157    #[test]
1158    fn test_acosh_basic_and_domain() {
1159        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AcoshFn));
1160        let ctx = interp(&wb);
1161        let f = ctx.context.get_function("", "ACOSH").unwrap();
1162        let a0 = make_num_ast(1.0);
1163        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1164        assert_eq!(
1165            f.dispatch(&args, &ctx.function_context(None))
1166                .unwrap()
1167                .into_literal(),
1168            LiteralValue::Number(0.0)
1169        );
1170        let a1 = make_num_ast(0.5);
1171        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
1172        match f
1173            .dispatch(&args2, &ctx.function_context(None))
1174            .unwrap()
1175            .into_literal()
1176        {
1177            LiteralValue::Error(e) => assert_eq!(e, "#NUM!"),
1178            v => panic!("expected error, got {v:?}"),
1179        }
1180    }
1181}
1182
1183#[derive(Debug)]
1184pub struct AtanhFn;
1185impl Function for AtanhFn {
1186    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1187    fn name(&self) -> &'static str {
1188        "ATANH"
1189    }
1190    fn min_args(&self) -> usize {
1191        1
1192    }
1193    fn arg_schema(&self) -> &'static [ArgSchema] {
1194        &ARG_ANY_ONE[..]
1195    }
1196    fn eval<'a, 'b, 'c>(
1197        &self,
1198        args: &'c [ArgumentHandle<'a, 'b>],
1199        _ctx: &dyn FunctionContext<'b>,
1200    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1201        let x = unary_numeric_arg(args)?;
1202        if x <= -1.0 || x >= 1.0 {
1203            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1204                ExcelError::new_num(),
1205            )));
1206        }
1207        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1208            x.atanh(),
1209        )))
1210    }
1211}
1212
1213#[cfg(test)]
1214mod tests_atanh {
1215    use super::*;
1216    use crate::test_workbook::TestWorkbook;
1217    use crate::traits::ArgumentHandle;
1218    use formualizer_parse::LiteralValue;
1219    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1220        wb.interpreter()
1221    }
1222    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1223        formualizer_parse::parser::ASTNode::new(
1224            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1225            None,
1226        )
1227    }
1228    fn assert_close(a: f64, b: f64) {
1229        assert!((a - b).abs() < 1e-9);
1230    }
1231    #[test]
1232    fn test_atanh_basic_and_domain() {
1233        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(AtanhFn));
1234        let ctx = interp(&wb);
1235        let f = ctx.context.get_function("", "ATANH").unwrap();
1236        let a0 = make_num_ast(0.5);
1237        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1238        match f
1239            .dispatch(&args, &ctx.function_context(None))
1240            .unwrap()
1241            .into_literal()
1242        {
1243            LiteralValue::Number(n) => assert_close(n, (0.5f64).atanh()),
1244            v => panic!("unexpected {v:?}"),
1245        }
1246        let a1 = make_num_ast(1.0);
1247        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
1248        match f
1249            .dispatch(&args2, &ctx.function_context(None))
1250            .unwrap()
1251            .into_literal()
1252        {
1253            LiteralValue::Error(e) => assert_eq!(e, "#NUM!"),
1254            v => panic!("expected error, got {v:?}"),
1255        }
1256    }
1257}
1258
1259#[derive(Debug)]
1260pub struct SechFn;
1261impl Function for SechFn {
1262    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1263    fn name(&self) -> &'static str {
1264        "SECH"
1265    }
1266    fn min_args(&self) -> usize {
1267        1
1268    }
1269    fn arg_schema(&self) -> &'static [ArgSchema] {
1270        &ARG_ANY_ONE[..]
1271    }
1272    fn eval<'a, 'b, 'c>(
1273        &self,
1274        args: &'c [ArgumentHandle<'a, 'b>],
1275        _ctx: &dyn FunctionContext<'b>,
1276    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1277        let x = unary_numeric_arg(args)?;
1278        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1279            1.0 / x.cosh(),
1280        )))
1281    }
1282}
1283
1284#[cfg(test)]
1285mod tests_sech {
1286    use super::*;
1287    use crate::test_workbook::TestWorkbook;
1288    use crate::traits::ArgumentHandle;
1289    use formualizer_parse::LiteralValue;
1290    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1291        wb.interpreter()
1292    }
1293    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1294        formualizer_parse::parser::ASTNode::new(
1295            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1296            None,
1297        )
1298    }
1299    fn assert_close(a: f64, b: f64) {
1300        assert!((a - b).abs() < 1e-9);
1301    }
1302    #[test]
1303    fn test_sech_basic() {
1304        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SechFn));
1305        let ctx = interp(&wb);
1306        let f = ctx.context.get_function("", "SECH").unwrap();
1307        let a0 = make_num_ast(0.0);
1308        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1309        match f
1310            .dispatch(&args, &ctx.function_context(None))
1311            .unwrap()
1312            .into_literal()
1313        {
1314            LiteralValue::Number(n) => assert_close(n, 1.0),
1315            v => panic!("unexpected {v:?}"),
1316        }
1317    }
1318}
1319
1320#[derive(Debug)]
1321pub struct CschFn;
1322impl Function for CschFn {
1323    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1324    fn name(&self) -> &'static str {
1325        "CSCH"
1326    }
1327    fn min_args(&self) -> usize {
1328        1
1329    }
1330    fn arg_schema(&self) -> &'static [ArgSchema] {
1331        &ARG_NUM_LENIENT_ONE[..]
1332    }
1333    fn eval<'a, 'b, 'c>(
1334        &self,
1335        args: &'c [ArgumentHandle<'a, 'b>],
1336        _ctx: &dyn FunctionContext<'b>,
1337    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1338        let x = unary_numeric_arg(args)?;
1339        let s = x.sinh(); // CSCH = 1/sinh(x), not 1/sin(x)
1340        if s.abs() < EPSILON_NEAR_ZERO {
1341            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1342                ExcelError::from_error_string("#DIV/0!"),
1343            )));
1344        }
1345        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1346            1.0 / s,
1347        )))
1348    }
1349}
1350
1351#[cfg(test)]
1352mod tests_csch {
1353    use super::*;
1354    use crate::test_workbook::TestWorkbook;
1355    use crate::traits::ArgumentHandle;
1356    use formualizer_parse::LiteralValue;
1357    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1358        wb.interpreter()
1359    }
1360    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1361        formualizer_parse::parser::ASTNode::new(
1362            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1363            None,
1364        )
1365    }
1366    fn assert_close(a: f64, b: f64) {
1367        assert!((a - b).abs() < 1e-9);
1368    }
1369    #[test]
1370    fn test_csch_basic_and_div0() {
1371        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CschFn));
1372        let ctx = interp(&wb);
1373        let csch = ctx.context.get_function("", "CSCH").unwrap();
1374        // CSCH(1) = 1/sinh(1) ~= 0.8509
1375        let a0 = make_num_ast(1.0);
1376        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1377        match csch
1378            .dispatch(&args, &ctx.function_context(None))
1379            .unwrap()
1380            .into_literal()
1381        {
1382            LiteralValue::Number(n) => assert_close(n, 0.8509181282393216),
1383            v => panic!("unexpected {v:?}"),
1384        }
1385        // CSCH(0) should be #DIV/0! since sinh(0) = 0
1386        let a1 = make_num_ast(0.0);
1387        let args2 = vec![ArgumentHandle::new(&a1, &ctx)];
1388        match csch
1389            .dispatch(&args2, &ctx.function_context(None))
1390            .unwrap()
1391            .into_literal()
1392        {
1393            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
1394            v => panic!("expected error, got {v:?}"),
1395        }
1396    }
1397}
1398
1399#[derive(Debug)]
1400pub struct CothFn;
1401impl Function for CothFn {
1402    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1403    fn name(&self) -> &'static str {
1404        "COTH"
1405    }
1406    fn min_args(&self) -> usize {
1407        1
1408    }
1409    fn arg_schema(&self) -> &'static [ArgSchema] {
1410        &ARG_NUM_LENIENT_ONE[..]
1411    }
1412    fn eval<'a, 'b, 'c>(
1413        &self,
1414        args: &'c [ArgumentHandle<'a, 'b>],
1415        _ctx: &dyn FunctionContext<'b>,
1416    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1417        let x = unary_numeric_arg(args)?;
1418        let s = x.sinh();
1419        if s.abs() < EPSILON_NEAR_ZERO {
1420            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1421                ExcelError::from_error_string("#DIV/0!"),
1422            )));
1423        }
1424        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1425            x.cosh() / s,
1426        )))
1427    }
1428}
1429
1430#[cfg(test)]
1431mod tests_coth {
1432    use super::*;
1433    use crate::test_workbook::TestWorkbook;
1434    use crate::traits::ArgumentHandle;
1435    use formualizer_parse::LiteralValue;
1436    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1437        wb.interpreter()
1438    }
1439    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1440        formualizer_parse::parser::ASTNode::new(
1441            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1442            None,
1443        )
1444    }
1445    #[test]
1446    fn test_coth_div0() {
1447        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(CothFn));
1448        let ctx = interp(&wb);
1449        let f = ctx.context.get_function("", "COTH").unwrap();
1450        let a0 = make_num_ast(0.0);
1451        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1452        match f
1453            .dispatch(&args, &ctx.function_context(None))
1454            .unwrap()
1455            .into_literal()
1456        {
1457            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
1458            v => panic!("expected error, got {v:?}"),
1459        }
1460    }
1461}
1462
1463/* ───────────────────── Angle conversion & constant ─────────────────── */
1464
1465#[derive(Debug)]
1466pub struct RadiansFn;
1467impl Function for RadiansFn {
1468    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1469    fn name(&self) -> &'static str {
1470        "RADIANS"
1471    }
1472    fn min_args(&self) -> usize {
1473        1
1474    }
1475    fn arg_schema(&self) -> &'static [ArgSchema] {
1476        &ARG_ANY_ONE[..]
1477    }
1478    fn eval<'a, 'b, 'c>(
1479        &self,
1480        args: &'c [ArgumentHandle<'a, 'b>],
1481        _ctx: &dyn FunctionContext<'b>,
1482    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1483        let deg = unary_numeric_arg(args)?;
1484        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1485            deg * PI / 180.0,
1486        )))
1487    }
1488}
1489
1490#[cfg(test)]
1491mod tests_radians {
1492    use super::*;
1493    use crate::test_workbook::TestWorkbook;
1494    use crate::traits::ArgumentHandle;
1495    use formualizer_parse::LiteralValue;
1496    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1497        wb.interpreter()
1498    }
1499    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1500        formualizer_parse::parser::ASTNode::new(
1501            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1502            None,
1503        )
1504    }
1505    fn assert_close(a: f64, b: f64) {
1506        assert!((a - b).abs() < 1e-9);
1507    }
1508    #[test]
1509    fn test_radians_basic() {
1510        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RadiansFn));
1511        let ctx = interp(&wb);
1512        let f = ctx.context.get_function("", "RADIANS").unwrap();
1513        let a0 = make_num_ast(180.0);
1514        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1515        match f
1516            .dispatch(&args, &ctx.function_context(None))
1517            .unwrap()
1518            .into_literal()
1519        {
1520            LiteralValue::Number(n) => assert_close(n, PI),
1521            v => panic!("unexpected {v:?}"),
1522        }
1523    }
1524}
1525
1526#[derive(Debug)]
1527pub struct DegreesFn;
1528impl Function for DegreesFn {
1529    func_caps!(PURE, ELEMENTWISE, NUMERIC_ONLY);
1530    fn name(&self) -> &'static str {
1531        "DEGREES"
1532    }
1533    fn min_args(&self) -> usize {
1534        1
1535    }
1536    fn arg_schema(&self) -> &'static [ArgSchema] {
1537        &ARG_ANY_ONE[..]
1538    }
1539    fn eval<'a, 'b, 'c>(
1540        &self,
1541        args: &'c [ArgumentHandle<'a, 'b>],
1542        _ctx: &dyn FunctionContext<'b>,
1543    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1544        let rad = unary_numeric_arg(args)?;
1545        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1546            rad * 180.0 / PI,
1547        )))
1548    }
1549}
1550
1551#[cfg(test)]
1552mod tests_degrees {
1553    use super::*;
1554    use crate::test_workbook::TestWorkbook;
1555    use crate::traits::ArgumentHandle;
1556    use formualizer_parse::LiteralValue;
1557    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1558        wb.interpreter()
1559    }
1560    fn make_num_ast(n: f64) -> formualizer_parse::parser::ASTNode {
1561        formualizer_parse::parser::ASTNode::new(
1562            formualizer_parse::parser::ASTNodeType::Literal(LiteralValue::Number(n)),
1563            None,
1564        )
1565    }
1566    fn assert_close(a: f64, b: f64) {
1567        assert!((a - b).abs() < 1e-9);
1568    }
1569    #[test]
1570    fn test_degrees_basic() {
1571        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(DegreesFn));
1572        let ctx = interp(&wb);
1573        let f = ctx.context.get_function("", "DEGREES").unwrap();
1574        let a0 = make_num_ast(PI);
1575        let args = vec![ArgumentHandle::new(&a0, &ctx)];
1576        match f
1577            .dispatch(&args, &ctx.function_context(None))
1578            .unwrap()
1579            .into_literal()
1580        {
1581            LiteralValue::Number(n) => assert_close(n, 180.0),
1582            v => panic!("unexpected {v:?}"),
1583        }
1584    }
1585}
1586
1587#[derive(Debug)]
1588pub struct PiFn;
1589impl Function for PiFn {
1590    func_caps!(PURE);
1591    fn name(&self) -> &'static str {
1592        "PI"
1593    }
1594    fn min_args(&self) -> usize {
1595        0
1596    }
1597    fn eval<'a, 'b, 'c>(
1598        &self,
1599        _args: &'c [ArgumentHandle<'a, 'b>],
1600        _ctx: &dyn FunctionContext<'b>,
1601    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1602        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(PI)))
1603    }
1604}
1605
1606#[cfg(test)]
1607mod tests_pi {
1608    use super::*;
1609    use crate::test_workbook::TestWorkbook;
1610    use formualizer_parse::LiteralValue;
1611    #[test]
1612    fn test_pi_basic() {
1613        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PiFn));
1614        let ctx = wb.interpreter();
1615        let f = ctx.context.get_function("", "PI").unwrap();
1616        assert_eq!(
1617            f.eval(&[], &ctx.function_context(None))
1618                .unwrap()
1619                .into_literal(),
1620            LiteralValue::Number(PI)
1621        );
1622    }
1623}
1624
1625pub fn register_builtins() {
1626    // --- Trigonometry: circular ---
1627    crate::function_registry::register_function(std::sync::Arc::new(SinFn));
1628    crate::function_registry::register_function(std::sync::Arc::new(CosFn));
1629    crate::function_registry::register_function(std::sync::Arc::new(TanFn));
1630    // A few elementwise numeric funcs are wired for map path; extend as needed
1631    crate::function_registry::register_function(std::sync::Arc::new(AsinFn));
1632    crate::function_registry::register_function(std::sync::Arc::new(AcosFn));
1633    crate::function_registry::register_function(std::sync::Arc::new(AtanFn));
1634    crate::function_registry::register_function(std::sync::Arc::new(Atan2Fn));
1635    crate::function_registry::register_function(std::sync::Arc::new(SecFn));
1636    crate::function_registry::register_function(std::sync::Arc::new(CscFn));
1637    crate::function_registry::register_function(std::sync::Arc::new(CotFn));
1638    crate::function_registry::register_function(std::sync::Arc::new(AcotFn));
1639
1640    // --- Trigonometry: hyperbolic ---
1641    crate::function_registry::register_function(std::sync::Arc::new(SinhFn));
1642    crate::function_registry::register_function(std::sync::Arc::new(CoshFn));
1643    crate::function_registry::register_function(std::sync::Arc::new(TanhFn));
1644    crate::function_registry::register_function(std::sync::Arc::new(AsinhFn));
1645    crate::function_registry::register_function(std::sync::Arc::new(AcoshFn));
1646    crate::function_registry::register_function(std::sync::Arc::new(AtanhFn));
1647    crate::function_registry::register_function(std::sync::Arc::new(SechFn));
1648    crate::function_registry::register_function(std::sync::Arc::new(CschFn));
1649    crate::function_registry::register_function(std::sync::Arc::new(CothFn));
1650
1651    // --- Angle conversion and constants ---
1652    crate::function_registry::register_function(std::sync::Arc::new(RadiansFn));
1653    crate::function_registry::register_function(std::sync::Arc::new(DegreesFn));
1654    crate::function_registry::register_function(std::sync::Arc::new(PiFn));
1655}