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