Skip to main content

formualizer_eval/builtins/
engineering.rs

1//! Engineering functions
2//! Bitwise: BITAND, BITOR, BITXOR, BITLSHIFT, BITRSHIFT
3
4use super::utils::{ARG_ANY_TWO, ARG_NUM_LENIENT_TWO, coerce_num};
5use crate::args::ArgSchema;
6use crate::function::Function;
7use crate::traits::{ArgumentHandle, FunctionContext};
8use formualizer_common::{ExcelError, LiteralValue};
9use formualizer_macros::func_caps;
10
11/// Helper to convert to integer for bitwise operations
12/// Excel's bitwise functions only work with non-negative integers up to 2^48
13fn to_bitwise_int(v: &LiteralValue) -> Result<i64, ExcelError> {
14    let n = coerce_num(v)?;
15    if n < 0.0 || n != n.trunc() || n >= 281474976710656.0 {
16        // 2^48
17        return Err(ExcelError::new_num());
18    }
19    Ok(n as i64)
20}
21
22/* ─────────────────────────── BITAND ──────────────────────────── */
23
24/// Returns the bitwise AND of two non-negative integers.
25///
26/// Combines matching bits from both inputs and keeps only bits set in both numbers.
27///
28/// # Remarks
29/// - Arguments are coerced to numbers and must be whole numbers in the range `[0, 2^48)`.
30/// - Returns `#NUM!` for negative values, fractional values, or values outside the supported range.
31/// - Propagates input errors.
32///
33/// # Examples
34/// ```yaml,sandbox
35/// title: "Mask selected bits"
36/// formula: "=BITAND(13,10)"
37/// expected: 8
38/// ```
39///
40/// ```yaml,sandbox
41/// title: "Check least-significant bit"
42/// formula: "=BITAND(7,1)"
43/// expected: 1
44/// ```
45/// ```yaml,docs
46/// related:
47///   - BITOR
48///   - BITXOR
49///   - BITLSHIFT
50/// faq:
51///   - q: "When does `BITAND` return `#NUM!`?"
52///     a: "Inputs must be whole numbers in `[0, 2^48)`; negatives, fractions, and out-of-range values return `#NUM!`."
53/// ```
54#[derive(Debug)]
55pub struct BitAndFn;
56/// [formualizer-docgen:schema:start]
57/// Name: BITAND
58/// Type: BitAndFn
59/// Min args: 2
60/// Max args: 2
61/// Variadic: false
62/// Signature: BITAND(arg1: number@scalar, arg2: number@scalar)
63/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
64/// Caps: PURE
65/// [formualizer-docgen:schema:end]
66impl Function for BitAndFn {
67    func_caps!(PURE);
68    fn name(&self) -> &'static str {
69        "BITAND"
70    }
71    fn min_args(&self) -> usize {
72        2
73    }
74    fn arg_schema(&self) -> &'static [ArgSchema] {
75        &ARG_NUM_LENIENT_TWO[..]
76    }
77    fn eval<'a, 'b, 'c>(
78        &self,
79        args: &'c [ArgumentHandle<'a, 'b>],
80        _ctx: &dyn FunctionContext<'b>,
81    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
82        let a = match args[0].value()?.into_literal() {
83            LiteralValue::Error(e) => {
84                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
85            }
86            other => match to_bitwise_int(&other) {
87                Ok(n) => n,
88                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
89            },
90        };
91        let b = match args[1].value()?.into_literal() {
92            LiteralValue::Error(e) => {
93                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
94            }
95            other => match to_bitwise_int(&other) {
96                Ok(n) => n,
97                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
98            },
99        };
100        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
101            (a & b) as f64,
102        )))
103    }
104}
105
106/* ─────────────────────────── BITOR ──────────────────────────── */
107
108/// Returns the bitwise OR of two non-negative integers.
109///
110/// Combines matching bits from both inputs and keeps bits set in either number.
111///
112/// # Remarks
113/// - Arguments are coerced to numbers and must be whole numbers in the range `[0, 2^48)`.
114/// - Returns `#NUM!` for negative values, fractional values, or out-of-range values.
115/// - Propagates input errors.
116///
117/// # Examples
118/// ```yaml,sandbox
119/// title: "Merge bit flags"
120/// formula: "=BITOR(13,10)"
121/// expected: 15
122/// ```
123///
124/// ```yaml,sandbox
125/// title: "Set an additional bit"
126/// formula: "=BITOR(8,1)"
127/// expected: 9
128/// ```
129/// ```yaml,docs
130/// related:
131///   - BITAND
132///   - BITXOR
133///   - BITRSHIFT
134/// faq:
135///   - q: "Does `BITOR` accept decimal-looking values like `3.0`?"
136///     a: "Yes if they coerce to whole integers; non-integer values still return `#NUM!`."
137/// ```
138#[derive(Debug)]
139pub struct BitOrFn;
140/// [formualizer-docgen:schema:start]
141/// Name: BITOR
142/// Type: BitOrFn
143/// Min args: 2
144/// Max args: 2
145/// Variadic: false
146/// Signature: BITOR(arg1: number@scalar, arg2: number@scalar)
147/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
148/// Caps: PURE
149/// [formualizer-docgen:schema:end]
150impl Function for BitOrFn {
151    func_caps!(PURE);
152    fn name(&self) -> &'static str {
153        "BITOR"
154    }
155    fn min_args(&self) -> usize {
156        2
157    }
158    fn arg_schema(&self) -> &'static [ArgSchema] {
159        &ARG_NUM_LENIENT_TWO[..]
160    }
161    fn eval<'a, 'b, 'c>(
162        &self,
163        args: &'c [ArgumentHandle<'a, 'b>],
164        _ctx: &dyn FunctionContext<'b>,
165    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
166        let a = match args[0].value()?.into_literal() {
167            LiteralValue::Error(e) => {
168                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
169            }
170            other => match to_bitwise_int(&other) {
171                Ok(n) => n,
172                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
173            },
174        };
175        let b = match args[1].value()?.into_literal() {
176            LiteralValue::Error(e) => {
177                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
178            }
179            other => match to_bitwise_int(&other) {
180                Ok(n) => n,
181                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
182            },
183        };
184        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
185            (a | b) as f64,
186        )))
187    }
188}
189
190/* ─────────────────────────── BITXOR ──────────────────────────── */
191
192/// Returns the bitwise exclusive OR of two non-negative integers.
193///
194/// Keeps bits that differ between the two inputs.
195///
196/// # Remarks
197/// - Arguments are coerced to numbers and must be whole numbers in the range `[0, 2^48)`.
198/// - Returns `#NUM!` for negative values, fractional values, or out-of-range values.
199/// - Propagates input errors.
200///
201/// # Examples
202/// ```yaml,sandbox
203/// title: "Highlight differing bits"
204/// formula: "=BITXOR(13,10)"
205/// expected: 7
206/// ```
207///
208/// ```yaml,sandbox
209/// title: "XOR identical values"
210/// formula: "=BITXOR(5,5)"
211/// expected: 0
212/// ```
213/// ```yaml,docs
214/// related:
215///   - BITAND
216///   - BITOR
217///   - BITLSHIFT
218/// faq:
219///   - q: "Why does `BITXOR(x, x)` return `0`?"
220///     a: "XOR keeps only differing bits; identical operands cancel every bit position."
221/// ```
222#[derive(Debug)]
223pub struct BitXorFn;
224/// [formualizer-docgen:schema:start]
225/// Name: BITXOR
226/// Type: BitXorFn
227/// Min args: 2
228/// Max args: 2
229/// Variadic: false
230/// Signature: BITXOR(arg1: number@scalar, arg2: number@scalar)
231/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
232/// Caps: PURE
233/// [formualizer-docgen:schema:end]
234impl Function for BitXorFn {
235    func_caps!(PURE);
236    fn name(&self) -> &'static str {
237        "BITXOR"
238    }
239    fn min_args(&self) -> usize {
240        2
241    }
242    fn arg_schema(&self) -> &'static [ArgSchema] {
243        &ARG_NUM_LENIENT_TWO[..]
244    }
245    fn eval<'a, 'b, 'c>(
246        &self,
247        args: &'c [ArgumentHandle<'a, 'b>],
248        _ctx: &dyn FunctionContext<'b>,
249    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
250        let a = match args[0].value()?.into_literal() {
251            LiteralValue::Error(e) => {
252                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
253            }
254            other => match to_bitwise_int(&other) {
255                Ok(n) => n,
256                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
257            },
258        };
259        let b = match args[1].value()?.into_literal() {
260            LiteralValue::Error(e) => {
261                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
262            }
263            other => match to_bitwise_int(&other) {
264                Ok(n) => n,
265                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
266            },
267        };
268        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
269            (a ^ b) as f64,
270        )))
271    }
272}
273
274/* ─────────────────────────── BITLSHIFT ──────────────────────────── */
275
276/// Shifts a non-negative integer left or right by a given bit count.
277///
278/// Positive `shift_amount` shifts left; negative `shift_amount` shifts right.
279///
280/// # Remarks
281/// - `number` must be a whole number in `[0, 2^48)`.
282/// - Shift values are numerically coerced; large positive shifts can return `#NUM!`.
283/// - Left-shift results must remain below `2^48`, or the function returns `#NUM!`.
284///
285/// # Examples
286/// ```yaml,sandbox
287/// title: "Shift left by two bits"
288/// formula: "=BITLSHIFT(6,2)"
289/// expected: 24
290/// ```
291///
292/// ```yaml,sandbox
293/// title: "Use negative shift to move right"
294/// formula: "=BITLSHIFT(32,-3)"
295/// expected: 4
296/// ```
297/// ```yaml,docs
298/// related:
299///   - BITRSHIFT
300///   - BITAND
301///   - BITOR
302/// faq:
303///   - q: "What does a negative `shift_amount` do in `BITLSHIFT`?"
304///     a: "Negative shifts are interpreted as right shifts, while positive shifts move bits left."
305/// ```
306#[derive(Debug)]
307pub struct BitLShiftFn;
308/// [formualizer-docgen:schema:start]
309/// Name: BITLSHIFT
310/// Type: BitLShiftFn
311/// Min args: 2
312/// Max args: 2
313/// Variadic: false
314/// Signature: BITLSHIFT(arg1: number@scalar, arg2: number@scalar)
315/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
316/// Caps: PURE
317/// [formualizer-docgen:schema:end]
318impl Function for BitLShiftFn {
319    func_caps!(PURE);
320    fn name(&self) -> &'static str {
321        "BITLSHIFT"
322    }
323    fn min_args(&self) -> usize {
324        2
325    }
326    fn arg_schema(&self) -> &'static [ArgSchema] {
327        &ARG_NUM_LENIENT_TWO[..]
328    }
329    fn eval<'a, 'b, 'c>(
330        &self,
331        args: &'c [ArgumentHandle<'a, 'b>],
332        _ctx: &dyn FunctionContext<'b>,
333    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
334        let n = match args[0].value()?.into_literal() {
335            LiteralValue::Error(e) => {
336                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
337            }
338            other => match to_bitwise_int(&other) {
339                Ok(n) => n,
340                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
341            },
342        };
343        let shift = match args[1].value()?.into_literal() {
344            LiteralValue::Error(e) => {
345                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
346            }
347            other => coerce_num(&other)? as i32,
348        };
349
350        // Negative shift means right shift
351        let result = if shift >= 0 {
352            if shift >= 48 {
353                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
354                    ExcelError::new_num(),
355                )));
356            }
357            let shifted = n << shift;
358            // Check if result exceeds 48-bit limit
359            if shifted >= 281474976710656 {
360                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
361                    ExcelError::new_num(),
362                )));
363            }
364            shifted
365        } else {
366            let rshift = (-shift) as u32;
367            if rshift >= 48 { 0 } else { n >> rshift }
368        };
369
370        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
371            result as f64,
372        )))
373    }
374}
375
376/* ─────────────────────────── BITRSHIFT ──────────────────────────── */
377
378/// Shifts a non-negative integer right or left by a given bit count.
379///
380/// Positive `shift_amount` shifts right; negative `shift_amount` shifts left.
381///
382/// # Remarks
383/// - `number` must be a whole number in `[0, 2^48)`.
384/// - Shift values are numerically coerced; large right shifts return `0`.
385/// - Negative shifts that overflow the 48-bit limit return `#NUM!`.
386///
387/// # Examples
388/// ```yaml,sandbox
389/// title: "Shift right by three bits"
390/// formula: "=BITRSHIFT(32,3)"
391/// expected: 4
392/// ```
393///
394/// ```yaml,sandbox
395/// title: "Use negative shift to move left"
396/// formula: "=BITRSHIFT(5,-1)"
397/// expected: 10
398/// ```
399/// ```yaml,docs
400/// related:
401///   - BITLSHIFT
402///   - BITAND
403///   - BITXOR
404/// faq:
405///   - q: "Why can negative shifts in `BITRSHIFT` return `#NUM!`?"
406///     a: "A negative shift means left-shift; if that left result exceeds the 48-bit limit, `#NUM!` is returned."
407/// ```
408#[derive(Debug)]
409pub struct BitRShiftFn;
410/// [formualizer-docgen:schema:start]
411/// Name: BITRSHIFT
412/// Type: BitRShiftFn
413/// Min args: 2
414/// Max args: 2
415/// Variadic: false
416/// Signature: BITRSHIFT(arg1: number@scalar, arg2: number@scalar)
417/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
418/// Caps: PURE
419/// [formualizer-docgen:schema:end]
420impl Function for BitRShiftFn {
421    func_caps!(PURE);
422    fn name(&self) -> &'static str {
423        "BITRSHIFT"
424    }
425    fn min_args(&self) -> usize {
426        2
427    }
428    fn arg_schema(&self) -> &'static [ArgSchema] {
429        &ARG_NUM_LENIENT_TWO[..]
430    }
431    fn eval<'a, 'b, 'c>(
432        &self,
433        args: &'c [ArgumentHandle<'a, 'b>],
434        _ctx: &dyn FunctionContext<'b>,
435    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
436        let n = match args[0].value()?.into_literal() {
437            LiteralValue::Error(e) => {
438                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
439            }
440            other => match to_bitwise_int(&other) {
441                Ok(n) => n,
442                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
443            },
444        };
445        let shift = match args[1].value()?.into_literal() {
446            LiteralValue::Error(e) => {
447                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
448            }
449            other => coerce_num(&other)? as i32,
450        };
451
452        // Negative shift means left shift
453        let result = if shift >= 0 {
454            if shift >= 48 { 0 } else { n >> shift }
455        } else {
456            let lshift = (-shift) as u32;
457            if lshift >= 48 {
458                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
459                    ExcelError::new_num(),
460                )));
461            }
462            let shifted = n << lshift;
463            // Check if result exceeds 48-bit limit
464            if shifted >= 281474976710656 {
465                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
466                    ExcelError::new_num(),
467                )));
468            }
469            shifted
470        };
471
472        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
473            result as f64,
474        )))
475    }
476}
477
478/* ─────────────────────────── Base Conversion Functions ──────────────────────────── */
479
480use super::utils::ARG_ANY_ONE;
481
482/// Helper to coerce value to text for base conversion
483fn coerce_base_text(v: &LiteralValue) -> Result<String, ExcelError> {
484    match v {
485        LiteralValue::Text(s) => Ok(s.clone()),
486        LiteralValue::Int(i) => Ok(i.to_string()),
487        LiteralValue::Number(n) => Ok((*n as i64).to_string()),
488        LiteralValue::Error(e) => Err(e.clone()),
489        _ => Err(ExcelError::new_value()),
490    }
491}
492
493/// Converts a binary text value to decimal.
494///
495/// Supports up to 10 binary digits, including two's-complement negative values.
496///
497/// # Remarks
498/// - Input is coerced to text and must contain only `0` and `1`.
499/// - 10-digit values starting with `1` are interpreted as signed two's-complement numbers.
500/// - Returns `#NUM!` for invalid characters or inputs longer than 10 digits.
501///
502/// # Examples
503/// ```yaml,sandbox
504/// title: "Convert an unsigned binary value"
505/// formula: "=BIN2DEC(\"101010\")"
506/// expected: 42
507/// ```
508///
509/// ```yaml,sandbox
510/// title: "Interpret signed 10-bit binary"
511/// formula: "=BIN2DEC(\"1111111111\")"
512/// expected: -1
513/// ```
514/// ```yaml,docs
515/// related:
516///   - DEC2BIN
517///   - BIN2HEX
518///   - BIN2OCT
519/// faq:
520///   - q: "How does `BIN2DEC` handle 10-bit values starting with `1`?"
521///     a: "They are interpreted as signed two's-complement values, so `1111111111` becomes `-1`."
522/// ```
523#[derive(Debug)]
524pub struct Bin2DecFn;
525/// [formualizer-docgen:schema:start]
526/// Name: BIN2DEC
527/// Type: Bin2DecFn
528/// Min args: 1
529/// Max args: 1
530/// Variadic: false
531/// Signature: BIN2DEC(arg1: any@scalar)
532/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
533/// Caps: PURE
534/// [formualizer-docgen:schema:end]
535impl Function for Bin2DecFn {
536    func_caps!(PURE);
537    fn name(&self) -> &'static str {
538        "BIN2DEC"
539    }
540    fn min_args(&self) -> usize {
541        1
542    }
543    fn arg_schema(&self) -> &'static [ArgSchema] {
544        &ARG_ANY_ONE[..]
545    }
546    fn eval<'a, 'b, 'c>(
547        &self,
548        args: &'c [ArgumentHandle<'a, 'b>],
549        _ctx: &dyn FunctionContext<'b>,
550    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
551        let text = match args[0].value()?.into_literal() {
552            LiteralValue::Error(e) => {
553                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
554            }
555            other => match coerce_base_text(&other) {
556                Ok(s) => s,
557                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
558            },
559        };
560
561        // Excel accepts 10-character binary (with sign bit)
562        if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
563            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
564                ExcelError::new_num(),
565            )));
566        }
567
568        // Handle two's complement for negative numbers (10 bits, first bit is sign)
569        let result = if text.len() == 10 && text.starts_with('1') {
570            // Negative number in two's complement
571            let val = i64::from_str_radix(&text, 2).unwrap_or(0);
572            val - 1024 // 2^10
573        } else {
574            i64::from_str_radix(&text, 2).unwrap_or(0)
575        };
576
577        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
578            result as f64,
579        )))
580    }
581}
582
583/// Converts a decimal integer to binary text.
584///
585/// Optionally pads the result with leading zeros using `places`.
586///
587/// # Remarks
588/// - `number` is coerced to an integer and must be in `[-512, 511]`.
589/// - Negative values are returned as 10-bit two's-complement binary strings.
590/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
591///
592/// # Examples
593/// ```yaml,sandbox
594/// title: "Convert a positive integer"
595/// formula: "=DEC2BIN(42)"
596/// expected: "101010"
597/// ```
598///
599/// ```yaml,sandbox
600/// title: "Pad binary output"
601/// formula: "=DEC2BIN(5,8)"
602/// expected: "00000101"
603/// ```
604/// ```yaml,docs
605/// related:
606///   - BIN2DEC
607///   - DEC2HEX
608///   - DEC2OCT
609/// faq:
610///   - q: "What limits apply to `DEC2BIN`?"
611///     a: "`number` must be in `[-512, 511]`, and optional `places` must be between output width and `10`, else `#NUM!`."
612/// ```
613#[derive(Debug)]
614pub struct Dec2BinFn;
615/// [formualizer-docgen:schema:start]
616/// Name: DEC2BIN
617/// Type: Dec2BinFn
618/// Min args: 1
619/// Max args: variadic
620/// Variadic: true
621/// Signature: DEC2BIN(arg1: number@scalar, arg2...: number@scalar)
622/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
623/// Caps: PURE
624/// [formualizer-docgen:schema:end]
625impl Function for Dec2BinFn {
626    func_caps!(PURE);
627    fn name(&self) -> &'static str {
628        "DEC2BIN"
629    }
630    fn min_args(&self) -> usize {
631        1
632    }
633    fn variadic(&self) -> bool {
634        true
635    }
636    fn arg_schema(&self) -> &'static [ArgSchema] {
637        &ARG_NUM_LENIENT_TWO[..]
638    }
639    fn eval<'a, 'b, 'c>(
640        &self,
641        args: &'c [ArgumentHandle<'a, 'b>],
642        _ctx: &dyn FunctionContext<'b>,
643    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
644        let n = match args[0].value()?.into_literal() {
645            LiteralValue::Error(e) => {
646                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
647            }
648            other => coerce_num(&other)? as i64,
649        };
650
651        // Excel limits: -512 to 511
652        if !(-512..=511).contains(&n) {
653            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
654                ExcelError::new_num(),
655            )));
656        }
657
658        let places = if args.len() > 1 {
659            match args[1].value()?.into_literal() {
660                LiteralValue::Error(e) => {
661                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
662                }
663                other => Some(coerce_num(&other)? as usize),
664            }
665        } else {
666            None
667        };
668
669        let binary = if n >= 0 {
670            format!("{:b}", n)
671        } else {
672            // Two's complement with 10 bits
673            format!("{:010b}", (n + 1024) as u64)
674        };
675
676        let result = if let Some(p) = places {
677            if p < binary.len() || p > 10 {
678                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
679                    ExcelError::new_num(),
680                )));
681            }
682            format!("{:0>width$}", binary, width = p)
683        } else {
684            binary
685        };
686
687        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
688    }
689}
690
691/// Converts a hexadecimal text value to decimal.
692///
693/// Supports up to 10 hex digits, including signed two's-complement values.
694///
695/// # Remarks
696/// - Input is coerced to text and must contain only hexadecimal characters.
697/// - 10-digit values beginning with `8`-`F` are interpreted as signed 40-bit numbers.
698/// - Returns `#NUM!` for invalid characters or inputs longer than 10 digits.
699///
700/// # Examples
701/// ```yaml,sandbox
702/// title: "Convert a positive hex value"
703/// formula: "=HEX2DEC(\"FF\")"
704/// expected: 255
705/// ```
706///
707/// ```yaml,sandbox
708/// title: "Interpret signed 40-bit hex"
709/// formula: "=HEX2DEC(\"FFFFFFFFFF\")"
710/// expected: -1
711/// ```
712/// ```yaml,docs
713/// related:
714///   - DEC2HEX
715///   - HEX2BIN
716///   - HEX2OCT
717/// faq:
718///   - q: "When is a 10-digit hex input treated as negative in `HEX2DEC`?"
719///     a: "If the first digit is `8` through `F`, it is decoded as signed 40-bit two's-complement."
720/// ```
721#[derive(Debug)]
722pub struct Hex2DecFn;
723/// [formualizer-docgen:schema:start]
724/// Name: HEX2DEC
725/// Type: Hex2DecFn
726/// Min args: 1
727/// Max args: 1
728/// Variadic: false
729/// Signature: HEX2DEC(arg1: any@scalar)
730/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
731/// Caps: PURE
732/// [formualizer-docgen:schema:end]
733impl Function for Hex2DecFn {
734    func_caps!(PURE);
735    fn name(&self) -> &'static str {
736        "HEX2DEC"
737    }
738    fn min_args(&self) -> usize {
739        1
740    }
741    fn arg_schema(&self) -> &'static [ArgSchema] {
742        &ARG_ANY_ONE[..]
743    }
744    fn eval<'a, 'b, 'c>(
745        &self,
746        args: &'c [ArgumentHandle<'a, 'b>],
747        _ctx: &dyn FunctionContext<'b>,
748    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
749        let text = match args[0].value()?.into_literal() {
750            LiteralValue::Error(e) => {
751                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
752            }
753            other => match coerce_base_text(&other) {
754                Ok(s) => s.to_uppercase(),
755                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
756            },
757        };
758
759        // Excel accepts 10-character hex
760        if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
761            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
762                ExcelError::new_num(),
763            )));
764        }
765
766        let result = if text.len() == 10 && text.starts_with(|c| c >= '8') {
767            // Negative number in two's complement (40 bits)
768            let val = i64::from_str_radix(&text, 16).unwrap_or(0);
769            val - (1i64 << 40)
770        } else {
771            i64::from_str_radix(&text, 16).unwrap_or(0)
772        };
773
774        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
775            result as f64,
776        )))
777    }
778}
779
780/// Converts a decimal integer to hexadecimal text.
781///
782/// Optionally pads the result with leading zeros using `places`.
783///
784/// # Remarks
785/// - `number` is coerced to an integer and must be in `[-2^39, 2^39 - 1]`.
786/// - Negative values are returned as 10-digit two's-complement hexadecimal strings.
787/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
788///
789/// # Examples
790/// ```yaml,sandbox
791/// title: "Convert decimal to hex"
792/// formula: "=DEC2HEX(255)"
793/// expected: "FF"
794/// ```
795///
796/// ```yaml,sandbox
797/// title: "Pad hexadecimal output"
798/// formula: "=DEC2HEX(31,4)"
799/// expected: "001F"
800/// ```
801/// ```yaml,docs
802/// related:
803///   - HEX2DEC
804///   - DEC2BIN
805///   - DEC2OCT
806/// faq:
807///   - q: "How are negative values formatted by `DEC2HEX`?"
808///     a: "Negative outputs use 10-digit two's-complement hexadecimal representation."
809/// ```
810#[derive(Debug)]
811pub struct Dec2HexFn;
812/// [formualizer-docgen:schema:start]
813/// Name: DEC2HEX
814/// Type: Dec2HexFn
815/// Min args: 1
816/// Max args: variadic
817/// Variadic: true
818/// Signature: DEC2HEX(arg1: number@scalar, arg2...: number@scalar)
819/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
820/// Caps: PURE
821/// [formualizer-docgen:schema:end]
822impl Function for Dec2HexFn {
823    func_caps!(PURE);
824    fn name(&self) -> &'static str {
825        "DEC2HEX"
826    }
827    fn min_args(&self) -> usize {
828        1
829    }
830    fn variadic(&self) -> bool {
831        true
832    }
833    fn arg_schema(&self) -> &'static [ArgSchema] {
834        &ARG_NUM_LENIENT_TWO[..]
835    }
836    fn eval<'a, 'b, 'c>(
837        &self,
838        args: &'c [ArgumentHandle<'a, 'b>],
839        _ctx: &dyn FunctionContext<'b>,
840    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
841        let n = match args[0].value()?.into_literal() {
842            LiteralValue::Error(e) => {
843                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
844            }
845            other => coerce_num(&other)? as i64,
846        };
847
848        // Excel limits
849        if !(-(1i64 << 39)..=(1i64 << 39) - 1).contains(&n) {
850            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
851                ExcelError::new_num(),
852            )));
853        }
854
855        let places = if args.len() > 1 {
856            match args[1].value()?.into_literal() {
857                LiteralValue::Error(e) => {
858                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
859                }
860                other => Some(coerce_num(&other)? as usize),
861            }
862        } else {
863            None
864        };
865
866        let hex = if n >= 0 {
867            format!("{:X}", n)
868        } else {
869            // Two's complement with 10 hex digits (40 bits)
870            format!("{:010X}", (n + (1i64 << 40)) as u64)
871        };
872
873        let result = if let Some(p) = places {
874            if p < hex.len() || p > 10 {
875                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
876                    ExcelError::new_num(),
877                )));
878            }
879            format!("{:0>width$}", hex, width = p)
880        } else {
881            hex
882        };
883
884        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
885    }
886}
887
888/// Converts an octal text value to decimal.
889///
890/// Supports up to 10 octal digits, including signed two's-complement values.
891///
892/// # Remarks
893/// - Input is coerced to text and must contain only digits `0` through `7`.
894/// - 10-digit values beginning with `4`-`7` are interpreted as signed 30-bit numbers.
895/// - Returns `#NUM!` for invalid characters or inputs longer than 10 digits.
896///
897/// # Examples
898/// ```yaml,sandbox
899/// title: "Convert positive octal"
900/// formula: "=OCT2DEC(\"17\")"
901/// expected: 15
902/// ```
903///
904/// ```yaml,sandbox
905/// title: "Interpret signed 30-bit octal"
906/// formula: "=OCT2DEC(\"7777777777\")"
907/// expected: -1
908/// ```
909/// ```yaml,docs
910/// related:
911///   - DEC2OCT
912///   - OCT2BIN
913///   - OCT2HEX
914/// faq:
915///   - q: "How does `OCT2DEC` interpret 10-digit values starting with `4`-`7`?"
916///     a: "Those are treated as signed 30-bit two's-complement octal values."
917/// ```
918#[derive(Debug)]
919pub struct Oct2DecFn;
920/// [formualizer-docgen:schema:start]
921/// Name: OCT2DEC
922/// Type: Oct2DecFn
923/// Min args: 1
924/// Max args: 1
925/// Variadic: false
926/// Signature: OCT2DEC(arg1: any@scalar)
927/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
928/// Caps: PURE
929/// [formualizer-docgen:schema:end]
930impl Function for Oct2DecFn {
931    func_caps!(PURE);
932    fn name(&self) -> &'static str {
933        "OCT2DEC"
934    }
935    fn min_args(&self) -> usize {
936        1
937    }
938    fn arg_schema(&self) -> &'static [ArgSchema] {
939        &ARG_ANY_ONE[..]
940    }
941    fn eval<'a, 'b, 'c>(
942        &self,
943        args: &'c [ArgumentHandle<'a, 'b>],
944        _ctx: &dyn FunctionContext<'b>,
945    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
946        let text = match args[0].value()?.into_literal() {
947            LiteralValue::Error(e) => {
948                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
949            }
950            other => match coerce_base_text(&other) {
951                Ok(s) => s,
952                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
953            },
954        };
955
956        // Excel accepts 10-character octal (30 bits)
957        if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
958            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
959                ExcelError::new_num(),
960            )));
961        }
962
963        let result = if text.len() == 10 && text.starts_with(|c| c >= '4') {
964            // Negative number in two's complement (30 bits)
965            let val = i64::from_str_radix(&text, 8).unwrap_or(0);
966            val - (1i64 << 30)
967        } else {
968            i64::from_str_radix(&text, 8).unwrap_or(0)
969        };
970
971        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
972            result as f64,
973        )))
974    }
975}
976
977/// Converts a decimal integer to octal text.
978///
979/// Optionally pads the result with leading zeros using `places`.
980///
981/// # Remarks
982/// - `number` is coerced to an integer and must be in `[-2^29, 2^29 - 1]`.
983/// - Negative values are returned as 10-digit two's-complement octal strings.
984/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
985///
986/// # Examples
987/// ```yaml,sandbox
988/// title: "Convert decimal to octal"
989/// formula: "=DEC2OCT(64)"
990/// expected: "100"
991/// ```
992///
993/// ```yaml,sandbox
994/// title: "Two's-complement negative output"
995/// formula: "=DEC2OCT(-1)"
996/// expected: "7777777777"
997/// ```
998/// ```yaml,docs
999/// related:
1000///   - OCT2DEC
1001///   - DEC2BIN
1002///   - DEC2HEX
1003/// faq:
1004///   - q: "What range does `DEC2OCT` support?"
1005///     a: "`number` must be in `[-2^29, 2^29 - 1]`; outside that range returns `#NUM!`."
1006/// ```
1007#[derive(Debug)]
1008pub struct Dec2OctFn;
1009/// [formualizer-docgen:schema:start]
1010/// Name: DEC2OCT
1011/// Type: Dec2OctFn
1012/// Min args: 1
1013/// Max args: variadic
1014/// Variadic: true
1015/// Signature: DEC2OCT(arg1: number@scalar, arg2...: number@scalar)
1016/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1017/// Caps: PURE
1018/// [formualizer-docgen:schema:end]
1019impl Function for Dec2OctFn {
1020    func_caps!(PURE);
1021    fn name(&self) -> &'static str {
1022        "DEC2OCT"
1023    }
1024    fn min_args(&self) -> usize {
1025        1
1026    }
1027    fn variadic(&self) -> bool {
1028        true
1029    }
1030    fn arg_schema(&self) -> &'static [ArgSchema] {
1031        &ARG_NUM_LENIENT_TWO[..]
1032    }
1033    fn eval<'a, 'b, 'c>(
1034        &self,
1035        args: &'c [ArgumentHandle<'a, 'b>],
1036        _ctx: &dyn FunctionContext<'b>,
1037    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1038        let n = match args[0].value()?.into_literal() {
1039            LiteralValue::Error(e) => {
1040                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1041            }
1042            other => coerce_num(&other)? as i64,
1043        };
1044
1045        // Excel limits: -536870912 to 536870911
1046        if !(-(1i64 << 29)..=(1i64 << 29) - 1).contains(&n) {
1047            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1048                ExcelError::new_num(),
1049            )));
1050        }
1051
1052        let places = if args.len() > 1 {
1053            match args[1].value()?.into_literal() {
1054                LiteralValue::Error(e) => {
1055                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1056                }
1057                other => Some(coerce_num(&other)? as usize),
1058            }
1059        } else {
1060            None
1061        };
1062
1063        let octal = if n >= 0 {
1064            format!("{:o}", n)
1065        } else {
1066            // Two's complement with 10 octal digits (30 bits)
1067            format!("{:010o}", (n + (1i64 << 30)) as u64)
1068        };
1069
1070        let result = if let Some(p) = places {
1071            if p < octal.len() || p > 10 {
1072                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1073                    ExcelError::new_num(),
1074                )));
1075            }
1076            format!("{:0>width$}", octal, width = p)
1077        } else {
1078            octal
1079        };
1080
1081        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1082    }
1083}
1084
1085/* ─────────────────────────── Cross-Base Conversions ──────────────────────────── */
1086
1087/// Converts a binary text value to hexadecimal text.
1088///
1089/// Optionally pads the output with leading zeros using `places`.
1090///
1091/// # Remarks
1092/// - Input must be a binary string up to 10 digits; 10-digit values may be signed.
1093/// - Signed binary values are converted using two's-complement semantics.
1094/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1095///
1096/// # Examples
1097/// ```yaml,sandbox
1098/// title: "Convert binary to hex"
1099/// formula: "=BIN2HEX(\"1010\")"
1100/// expected: "A"
1101/// ```
1102///
1103/// ```yaml,sandbox
1104/// title: "Pad hexadecimal output"
1105/// formula: "=BIN2HEX(\"1010\",4)"
1106/// expected: "000A"
1107/// ```
1108/// ```yaml,docs
1109/// related:
1110///   - HEX2BIN
1111///   - BIN2DEC
1112///   - DEC2HEX
1113/// faq:
1114///   - q: "Does `BIN2HEX` preserve signed binary meaning?"
1115///     a: "Yes. A 10-bit binary with leading `1` is interpreted as signed and converted using two's-complement semantics."
1116/// ```
1117#[derive(Debug)]
1118pub struct Bin2HexFn;
1119/// [formualizer-docgen:schema:start]
1120/// Name: BIN2HEX
1121/// Type: Bin2HexFn
1122/// Min args: 1
1123/// Max args: variadic
1124/// Variadic: true
1125/// Signature: BIN2HEX(arg1: number@scalar, arg2...: number@scalar)
1126/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1127/// Caps: PURE
1128/// [formualizer-docgen:schema:end]
1129impl Function for Bin2HexFn {
1130    func_caps!(PURE);
1131    fn name(&self) -> &'static str {
1132        "BIN2HEX"
1133    }
1134    fn min_args(&self) -> usize {
1135        1
1136    }
1137    fn variadic(&self) -> bool {
1138        true
1139    }
1140    fn arg_schema(&self) -> &'static [ArgSchema] {
1141        &ARG_NUM_LENIENT_TWO[..]
1142    }
1143    fn eval<'a, 'b, 'c>(
1144        &self,
1145        args: &'c [ArgumentHandle<'a, 'b>],
1146        _ctx: &dyn FunctionContext<'b>,
1147    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1148        let text = match args[0].value()?.into_literal() {
1149            LiteralValue::Error(e) => {
1150                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1151            }
1152            other => match coerce_base_text(&other) {
1153                Ok(s) => s,
1154                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1155            },
1156        };
1157
1158        if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
1159            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1160                ExcelError::new_num(),
1161            )));
1162        }
1163
1164        // Convert binary to decimal first
1165        let dec = if text.len() == 10 && text.starts_with('1') {
1166            let val = i64::from_str_radix(&text, 2).unwrap_or(0);
1167            val - 1024
1168        } else {
1169            i64::from_str_radix(&text, 2).unwrap_or(0)
1170        };
1171
1172        let places = if args.len() > 1 {
1173            match args[1].value()?.into_literal() {
1174                LiteralValue::Error(e) => {
1175                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1176                }
1177                other => Some(coerce_num(&other)? as usize),
1178            }
1179        } else {
1180            None
1181        };
1182
1183        let hex = if dec >= 0 {
1184            format!("{:X}", dec)
1185        } else {
1186            format!("{:010X}", (dec + (1i64 << 40)) as u64)
1187        };
1188
1189        let result = if let Some(p) = places {
1190            if p < hex.len() || p > 10 {
1191                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1192                    ExcelError::new_num(),
1193                )));
1194            }
1195            format!("{:0>width$}", hex, width = p)
1196        } else {
1197            hex
1198        };
1199
1200        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1201    }
1202}
1203
1204/// Converts a hexadecimal text value to binary text.
1205///
1206/// Supports optional left-padding through the `places` argument.
1207///
1208/// # Remarks
1209/// - Input must be hexadecimal text up to 10 characters and may be signed two's-complement.
1210/// - The converted decimal value must be in `[-512, 511]`, or the function returns `#NUM!`.
1211/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1212///
1213/// # Examples
1214/// ```yaml,sandbox
1215/// title: "Convert positive hex to binary"
1216/// formula: "=HEX2BIN(\"1F\")"
1217/// expected: "11111"
1218/// ```
1219///
1220/// ```yaml,sandbox
1221/// title: "Convert signed hex"
1222/// formula: "=HEX2BIN(\"FFFFFFFFFF\")"
1223/// expected: "1111111111"
1224/// ```
1225/// ```yaml,docs
1226/// related:
1227///   - BIN2HEX
1228///   - HEX2DEC
1229///   - DEC2BIN
1230/// faq:
1231///   - q: "Why can valid hex text still produce `#NUM!` in `HEX2BIN`?"
1232///     a: "After conversion, the decimal value must fit `[-512, 511]`; otherwise binary output is rejected."
1233/// ```
1234#[derive(Debug)]
1235pub struct Hex2BinFn;
1236/// [formualizer-docgen:schema:start]
1237/// Name: HEX2BIN
1238/// Type: Hex2BinFn
1239/// Min args: 1
1240/// Max args: variadic
1241/// Variadic: true
1242/// Signature: HEX2BIN(arg1: any@scalar, arg2...: any@scalar)
1243/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
1244/// Caps: PURE
1245/// [formualizer-docgen:schema:end]
1246impl Function for Hex2BinFn {
1247    func_caps!(PURE);
1248    fn name(&self) -> &'static str {
1249        "HEX2BIN"
1250    }
1251    fn min_args(&self) -> usize {
1252        1
1253    }
1254    fn variadic(&self) -> bool {
1255        true
1256    }
1257    fn arg_schema(&self) -> &'static [ArgSchema] {
1258        &ARG_ANY_TWO[..]
1259    }
1260    fn eval<'a, 'b, 'c>(
1261        &self,
1262        args: &'c [ArgumentHandle<'a, 'b>],
1263        _ctx: &dyn FunctionContext<'b>,
1264    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1265        let text = match args[0].value()?.into_literal() {
1266            LiteralValue::Error(e) => {
1267                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1268            }
1269            other => match coerce_base_text(&other) {
1270                Ok(s) => s.to_uppercase(),
1271                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1272            },
1273        };
1274
1275        if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
1276            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1277                ExcelError::new_num(),
1278            )));
1279        }
1280
1281        // Convert hex to decimal first
1282        let dec = if text.len() == 10 && text.starts_with(|c| c >= '8') {
1283            let val = i64::from_str_radix(&text, 16).unwrap_or(0);
1284            val - (1i64 << 40)
1285        } else {
1286            i64::from_str_radix(&text, 16).unwrap_or(0)
1287        };
1288
1289        // Check range for binary output (-512 to 511)
1290        if !(-512..=511).contains(&dec) {
1291            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1292                ExcelError::new_num(),
1293            )));
1294        }
1295
1296        let places = if args.len() > 1 {
1297            match args[1].value()?.into_literal() {
1298                LiteralValue::Error(e) => {
1299                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1300                }
1301                other => Some(coerce_num(&other)? as usize),
1302            }
1303        } else {
1304            None
1305        };
1306
1307        let binary = if dec >= 0 {
1308            format!("{:b}", dec)
1309        } else {
1310            format!("{:010b}", (dec + 1024) as u64)
1311        };
1312
1313        let result = if let Some(p) = places {
1314            if p < binary.len() || p > 10 {
1315                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1316                    ExcelError::new_num(),
1317                )));
1318            }
1319            format!("{:0>width$}", binary, width = p)
1320        } else {
1321            binary
1322        };
1323
1324        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1325    }
1326}
1327
1328/// Converts a binary text value to octal text.
1329///
1330/// Supports optional left-padding through the `places` argument.
1331///
1332/// # Remarks
1333/// - Input must be binary text up to 10 digits and may be signed two's-complement.
1334/// - Signed values are preserved through conversion to octal.
1335/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1336///
1337/// # Examples
1338/// ```yaml,sandbox
1339/// title: "Convert binary to octal"
1340/// formula: "=BIN2OCT(\"111111\")"
1341/// expected: "77"
1342/// ```
1343///
1344/// ```yaml,sandbox
1345/// title: "Pad octal output"
1346/// formula: "=BIN2OCT(\"111111\",4)"
1347/// expected: "0077"
1348/// ```
1349/// ```yaml,docs
1350/// related:
1351///   - OCT2BIN
1352///   - BIN2DEC
1353///   - DEC2OCT
1354/// faq:
1355///   - q: "How are signed 10-bit binaries handled by `BIN2OCT`?"
1356///     a: "They are first decoded as signed decimal and then re-encoded to octal with two's-complement output for negatives."
1357/// ```
1358#[derive(Debug)]
1359pub struct Bin2OctFn;
1360/// [formualizer-docgen:schema:start]
1361/// Name: BIN2OCT
1362/// Type: Bin2OctFn
1363/// Min args: 1
1364/// Max args: variadic
1365/// Variadic: true
1366/// Signature: BIN2OCT(arg1: number@scalar, arg2...: number@scalar)
1367/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1368/// Caps: PURE
1369/// [formualizer-docgen:schema:end]
1370impl Function for Bin2OctFn {
1371    func_caps!(PURE);
1372    fn name(&self) -> &'static str {
1373        "BIN2OCT"
1374    }
1375    fn min_args(&self) -> usize {
1376        1
1377    }
1378    fn variadic(&self) -> bool {
1379        true
1380    }
1381    fn arg_schema(&self) -> &'static [ArgSchema] {
1382        &ARG_NUM_LENIENT_TWO[..]
1383    }
1384    fn eval<'a, 'b, 'c>(
1385        &self,
1386        args: &'c [ArgumentHandle<'a, 'b>],
1387        _ctx: &dyn FunctionContext<'b>,
1388    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1389        let text = match args[0].value()?.into_literal() {
1390            LiteralValue::Error(e) => {
1391                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1392            }
1393            other => match coerce_base_text(&other) {
1394                Ok(s) => s,
1395                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1396            },
1397        };
1398
1399        if text.len() > 10 || !text.chars().all(|c| c == '0' || c == '1') {
1400            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1401                ExcelError::new_num(),
1402            )));
1403        }
1404
1405        let dec = if text.len() == 10 && text.starts_with('1') {
1406            let val = i64::from_str_radix(&text, 2).unwrap_or(0);
1407            val - 1024
1408        } else {
1409            i64::from_str_radix(&text, 2).unwrap_or(0)
1410        };
1411
1412        let places = if args.len() > 1 {
1413            match args[1].value()?.into_literal() {
1414                LiteralValue::Error(e) => {
1415                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1416                }
1417                other => Some(coerce_num(&other)? as usize),
1418            }
1419        } else {
1420            None
1421        };
1422
1423        let octal = if dec >= 0 {
1424            format!("{:o}", dec)
1425        } else {
1426            format!("{:010o}", (dec + (1i64 << 30)) as u64)
1427        };
1428
1429        let result = if let Some(p) = places {
1430            if p < octal.len() || p > 10 {
1431                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1432                    ExcelError::new_num(),
1433                )));
1434            }
1435            format!("{:0>width$}", octal, width = p)
1436        } else {
1437            octal
1438        };
1439
1440        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1441    }
1442}
1443
1444/// Converts an octal text value to binary text.
1445///
1446/// Supports optional left-padding through the `places` argument.
1447///
1448/// # Remarks
1449/// - Input must be octal text up to 10 digits and may be signed two's-complement.
1450/// - Converted values must fall in `[-512, 511]`, or the function returns `#NUM!`.
1451/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1452///
1453/// # Examples
1454/// ```yaml,sandbox
1455/// title: "Convert octal to binary"
1456/// formula: "=OCT2BIN(\"77\")"
1457/// expected: "111111"
1458/// ```
1459///
1460/// ```yaml,sandbox
1461/// title: "Convert signed octal"
1462/// formula: "=OCT2BIN(\"7777777777\")"
1463/// expected: "1111111111"
1464/// ```
1465/// ```yaml,docs
1466/// related:
1467///   - BIN2OCT
1468///   - OCT2DEC
1469///   - DEC2BIN
1470/// faq:
1471///   - q: "Why does `OCT2BIN` return `#NUM!` for some octal inputs?"
1472///     a: "After decoding, the value must be within `[-512, 511]` to be representable in Excel-style binary output."
1473/// ```
1474#[derive(Debug)]
1475pub struct Oct2BinFn;
1476/// [formualizer-docgen:schema:start]
1477/// Name: OCT2BIN
1478/// Type: Oct2BinFn
1479/// Min args: 1
1480/// Max args: variadic
1481/// Variadic: true
1482/// Signature: OCT2BIN(arg1: number@scalar, arg2...: number@scalar)
1483/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1484/// Caps: PURE
1485/// [formualizer-docgen:schema:end]
1486impl Function for Oct2BinFn {
1487    func_caps!(PURE);
1488    fn name(&self) -> &'static str {
1489        "OCT2BIN"
1490    }
1491    fn min_args(&self) -> usize {
1492        1
1493    }
1494    fn variadic(&self) -> bool {
1495        true
1496    }
1497    fn arg_schema(&self) -> &'static [ArgSchema] {
1498        &ARG_NUM_LENIENT_TWO[..]
1499    }
1500    fn eval<'a, 'b, 'c>(
1501        &self,
1502        args: &'c [ArgumentHandle<'a, 'b>],
1503        _ctx: &dyn FunctionContext<'b>,
1504    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1505        let text = match args[0].value()?.into_literal() {
1506            LiteralValue::Error(e) => {
1507                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1508            }
1509            other => match coerce_base_text(&other) {
1510                Ok(s) => s,
1511                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1512            },
1513        };
1514
1515        if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
1516            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1517                ExcelError::new_num(),
1518            )));
1519        }
1520
1521        let dec = if text.len() == 10 && text.starts_with(|c| c >= '4') {
1522            let val = i64::from_str_radix(&text, 8).unwrap_or(0);
1523            val - (1i64 << 30)
1524        } else {
1525            i64::from_str_radix(&text, 8).unwrap_or(0)
1526        };
1527
1528        // Check range for binary output (-512 to 511)
1529        if !(-512..=511).contains(&dec) {
1530            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1531                ExcelError::new_num(),
1532            )));
1533        }
1534
1535        let places = if args.len() > 1 {
1536            match args[1].value()?.into_literal() {
1537                LiteralValue::Error(e) => {
1538                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1539                }
1540                other => Some(coerce_num(&other)? as usize),
1541            }
1542        } else {
1543            None
1544        };
1545
1546        let binary = if dec >= 0 {
1547            format!("{:b}", dec)
1548        } else {
1549            format!("{:010b}", (dec + 1024) as u64)
1550        };
1551
1552        let result = if let Some(p) = places {
1553            if p < binary.len() || p > 10 {
1554                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1555                    ExcelError::new_num(),
1556                )));
1557            }
1558            format!("{:0>width$}", binary, width = p)
1559        } else {
1560            binary
1561        };
1562
1563        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1564    }
1565}
1566
1567/// Converts a hexadecimal text value to octal text.
1568///
1569/// Supports optional left-padding through the `places` argument.
1570///
1571/// # Remarks
1572/// - Input must be hexadecimal text up to 10 characters and may be signed two's-complement.
1573/// - Converted values must fit the octal range `[-2^29, 2^29 - 1]`, or `#NUM!` is returned.
1574/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1575///
1576/// # Examples
1577/// ```yaml,sandbox
1578/// title: "Convert hex to octal"
1579/// formula: "=HEX2OCT(\"1F\")"
1580/// expected: "37"
1581/// ```
1582///
1583/// ```yaml,sandbox
1584/// title: "Convert signed hex"
1585/// formula: "=HEX2OCT(\"FFFFFFFFFF\")"
1586/// expected: "7777777777"
1587/// ```
1588/// ```yaml,docs
1589/// related:
1590///   - OCT2HEX
1591///   - HEX2DEC
1592///   - DEC2OCT
1593/// faq:
1594///   - q: "What causes `HEX2OCT` to return `#NUM!`?"
1595///     a: "The decoded value must fit octal output range `[-2^29, 2^29 - 1]`, and optional `places` must be valid."
1596/// ```
1597#[derive(Debug)]
1598pub struct Hex2OctFn;
1599/// [formualizer-docgen:schema:start]
1600/// Name: HEX2OCT
1601/// Type: Hex2OctFn
1602/// Min args: 1
1603/// Max args: variadic
1604/// Variadic: true
1605/// Signature: HEX2OCT(arg1: any@scalar, arg2...: any@scalar)
1606/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
1607/// Caps: PURE
1608/// [formualizer-docgen:schema:end]
1609impl Function for Hex2OctFn {
1610    func_caps!(PURE);
1611    fn name(&self) -> &'static str {
1612        "HEX2OCT"
1613    }
1614    fn min_args(&self) -> usize {
1615        1
1616    }
1617    fn variadic(&self) -> bool {
1618        true
1619    }
1620    fn arg_schema(&self) -> &'static [ArgSchema] {
1621        &ARG_ANY_TWO[..]
1622    }
1623    fn eval<'a, 'b, 'c>(
1624        &self,
1625        args: &'c [ArgumentHandle<'a, 'b>],
1626        _ctx: &dyn FunctionContext<'b>,
1627    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1628        let text = match args[0].value()?.into_literal() {
1629            LiteralValue::Error(e) => {
1630                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1631            }
1632            other => match coerce_base_text(&other) {
1633                Ok(s) => s.to_uppercase(),
1634                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1635            },
1636        };
1637
1638        if text.len() > 10 || !text.chars().all(|c| c.is_ascii_hexdigit()) {
1639            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1640                ExcelError::new_num(),
1641            )));
1642        }
1643
1644        let dec = if text.len() == 10 && text.starts_with(|c| c >= '8') {
1645            let val = i64::from_str_radix(&text, 16).unwrap_or(0);
1646            val - (1i64 << 40)
1647        } else {
1648            i64::from_str_radix(&text, 16).unwrap_or(0)
1649        };
1650
1651        // Check range for octal output
1652        if !(-(1i64 << 29)..=(1i64 << 29) - 1).contains(&dec) {
1653            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1654                ExcelError::new_num(),
1655            )));
1656        }
1657
1658        let places = if args.len() > 1 {
1659            match args[1].value()?.into_literal() {
1660                LiteralValue::Error(e) => {
1661                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1662                }
1663                other => Some(coerce_num(&other)? as usize),
1664            }
1665        } else {
1666            None
1667        };
1668
1669        let octal = if dec >= 0 {
1670            format!("{:o}", dec)
1671        } else {
1672            format!("{:010o}", (dec + (1i64 << 30)) as u64)
1673        };
1674
1675        let result = if let Some(p) = places {
1676            if p < octal.len() || p > 10 {
1677                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1678                    ExcelError::new_num(),
1679                )));
1680            }
1681            format!("{:0>width$}", octal, width = p)
1682        } else {
1683            octal
1684        };
1685
1686        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1687    }
1688}
1689
1690/// Converts an octal text value to hexadecimal text.
1691///
1692/// Supports optional left-padding through the `places` argument.
1693///
1694/// # Remarks
1695/// - Input must be octal text up to 10 digits and may be signed two's-complement.
1696/// - Signed values are converted through their decimal representation.
1697/// - `places` must be at least the output width and at most `10`, or `#NUM!` is returned.
1698///
1699/// # Examples
1700/// ```yaml,sandbox
1701/// title: "Convert octal to hex"
1702/// formula: "=OCT2HEX(\"77\")"
1703/// expected: "3F"
1704/// ```
1705///
1706/// ```yaml,sandbox
1707/// title: "Convert signed octal"
1708/// formula: "=OCT2HEX(\"7777777777\")"
1709/// expected: "FFFFFFFFFF"
1710/// ```
1711/// ```yaml,docs
1712/// related:
1713///   - HEX2OCT
1714///   - OCT2DEC
1715///   - DEC2HEX
1716/// faq:
1717///   - q: "How does `OCT2HEX` treat signed octal input?"
1718///     a: "Signed 10-digit octal is decoded via two's-complement and then emitted as hex, preserving signed meaning."
1719/// ```
1720#[derive(Debug)]
1721pub struct Oct2HexFn;
1722/// [formualizer-docgen:schema:start]
1723/// Name: OCT2HEX
1724/// Type: Oct2HexFn
1725/// Min args: 1
1726/// Max args: variadic
1727/// Variadic: true
1728/// Signature: OCT2HEX(arg1: number@scalar, arg2...: number@scalar)
1729/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1730/// Caps: PURE
1731/// [formualizer-docgen:schema:end]
1732impl Function for Oct2HexFn {
1733    func_caps!(PURE);
1734    fn name(&self) -> &'static str {
1735        "OCT2HEX"
1736    }
1737    fn min_args(&self) -> usize {
1738        1
1739    }
1740    fn variadic(&self) -> bool {
1741        true
1742    }
1743    fn arg_schema(&self) -> &'static [ArgSchema] {
1744        &ARG_NUM_LENIENT_TWO[..]
1745    }
1746    fn eval<'a, 'b, 'c>(
1747        &self,
1748        args: &'c [ArgumentHandle<'a, 'b>],
1749        _ctx: &dyn FunctionContext<'b>,
1750    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1751        let text = match args[0].value()?.into_literal() {
1752            LiteralValue::Error(e) => {
1753                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1754            }
1755            other => match coerce_base_text(&other) {
1756                Ok(s) => s,
1757                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1758            },
1759        };
1760
1761        if text.len() > 10 || !text.chars().all(|c| ('0'..='7').contains(&c)) {
1762            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1763                ExcelError::new_num(),
1764            )));
1765        }
1766
1767        let dec = if text.len() == 10 && text.starts_with(|c| c >= '4') {
1768            let val = i64::from_str_radix(&text, 8).unwrap_or(0);
1769            val - (1i64 << 30)
1770        } else {
1771            i64::from_str_radix(&text, 8).unwrap_or(0)
1772        };
1773
1774        let places = if args.len() > 1 {
1775            match args[1].value()?.into_literal() {
1776                LiteralValue::Error(e) => {
1777                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1778                }
1779                other => Some(coerce_num(&other)? as usize),
1780            }
1781        } else {
1782            None
1783        };
1784
1785        let hex = if dec >= 0 {
1786            format!("{:X}", dec)
1787        } else {
1788            format!("{:010X}", (dec + (1i64 << 40)) as u64)
1789        };
1790
1791        let result = if let Some(p) = places {
1792            if p < hex.len() || p > 10 {
1793                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1794                    ExcelError::new_num(),
1795                )));
1796            }
1797            format!("{:0>width$}", hex, width = p)
1798        } else {
1799            hex
1800        };
1801
1802        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
1803    }
1804}
1805
1806/* ─────────────────────────── Engineering Comparison Functions ──────────────────────────── */
1807
1808/// Tests whether two numbers are equal.
1809///
1810/// Returns `1` when values match and `0` otherwise.
1811///
1812/// # Remarks
1813/// - If `number2` is omitted, it defaults to `0`.
1814/// - Inputs are numerically coerced.
1815/// - Uses a small numeric tolerance for floating-point comparison.
1816///
1817/// # Examples
1818/// ```yaml,sandbox
1819/// title: "Equal values"
1820/// formula: "=DELTA(5,5)"
1821/// expected: 1
1822/// ```
1823///
1824/// ```yaml,sandbox
1825/// title: "Default second argument"
1826/// formula: "=DELTA(2.5)"
1827/// expected: 0
1828/// ```
1829/// ```yaml,docs
1830/// related:
1831///   - GESTEP
1832/// faq:
1833///   - q: "Does `DELTA` require exact floating-point equality?"
1834///     a: "It uses a small tolerance (`1e-12`), so values that differ only by tiny floating noise compare as equal."
1835/// ```
1836#[derive(Debug)]
1837pub struct DeltaFn;
1838/// [formualizer-docgen:schema:start]
1839/// Name: DELTA
1840/// Type: DeltaFn
1841/// Min args: 1
1842/// Max args: variadic
1843/// Variadic: true
1844/// Signature: DELTA(arg1: number@scalar, arg2...: number@scalar)
1845/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1846/// Caps: PURE
1847/// [formualizer-docgen:schema:end]
1848impl Function for DeltaFn {
1849    func_caps!(PURE);
1850    fn name(&self) -> &'static str {
1851        "DELTA"
1852    }
1853    fn min_args(&self) -> usize {
1854        1
1855    }
1856    fn variadic(&self) -> bool {
1857        true
1858    }
1859    fn arg_schema(&self) -> &'static [ArgSchema] {
1860        &ARG_NUM_LENIENT_TWO[..]
1861    }
1862    fn eval<'a, 'b, 'c>(
1863        &self,
1864        args: &'c [ArgumentHandle<'a, 'b>],
1865        _ctx: &dyn FunctionContext<'b>,
1866    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1867        let n1 = match args[0].value()?.into_literal() {
1868            LiteralValue::Error(e) => {
1869                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1870            }
1871            other => coerce_num(&other)?,
1872        };
1873        let n2 = if args.len() > 1 {
1874            match args[1].value()?.into_literal() {
1875                LiteralValue::Error(e) => {
1876                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1877                }
1878                other => coerce_num(&other)?,
1879            }
1880        } else {
1881            0.0
1882        };
1883
1884        let result = if (n1 - n2).abs() < 1e-12 { 1.0 } else { 0.0 };
1885        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1886            result,
1887        )))
1888    }
1889}
1890
1891/// Returns `1` when a number is greater than or equal to a step value.
1892///
1893/// Returns `0` when the number is below the step.
1894///
1895/// # Remarks
1896/// - If `step` is omitted, it defaults to `0`.
1897/// - Inputs are numerically coerced.
1898/// - Propagates input errors.
1899///
1900/// # Examples
1901/// ```yaml,sandbox
1902/// title: "Value meets threshold"
1903/// formula: "=GESTEP(5,3)"
1904/// expected: 1
1905/// ```
1906///
1907/// ```yaml,sandbox
1908/// title: "Default threshold of zero"
1909/// formula: "=GESTEP(-2)"
1910/// expected: 0
1911/// ```
1912/// ```yaml,docs
1913/// related:
1914///   - DELTA
1915/// faq:
1916///   - q: "What default threshold does `GESTEP` use?"
1917///     a: "If omitted, `step` defaults to `0`, so the function returns `1` for non-negative inputs."
1918/// ```
1919#[derive(Debug)]
1920pub struct GestepFn;
1921/// [formualizer-docgen:schema:start]
1922/// Name: GESTEP
1923/// Type: GestepFn
1924/// Min args: 1
1925/// Max args: variadic
1926/// Variadic: true
1927/// Signature: GESTEP(arg1: number@scalar, arg2...: number@scalar)
1928/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
1929/// Caps: PURE
1930/// [formualizer-docgen:schema:end]
1931impl Function for GestepFn {
1932    func_caps!(PURE);
1933    fn name(&self) -> &'static str {
1934        "GESTEP"
1935    }
1936    fn min_args(&self) -> usize {
1937        1
1938    }
1939    fn variadic(&self) -> bool {
1940        true
1941    }
1942    fn arg_schema(&self) -> &'static [ArgSchema] {
1943        &ARG_NUM_LENIENT_TWO[..]
1944    }
1945    fn eval<'a, 'b, 'c>(
1946        &self,
1947        args: &'c [ArgumentHandle<'a, 'b>],
1948        _ctx: &dyn FunctionContext<'b>,
1949    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
1950        let n = match args[0].value()?.into_literal() {
1951            LiteralValue::Error(e) => {
1952                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1953            }
1954            other => coerce_num(&other)?,
1955        };
1956        let step = if args.len() > 1 {
1957            match args[1].value()?.into_literal() {
1958                LiteralValue::Error(e) => {
1959                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
1960                }
1961                other => coerce_num(&other)?,
1962            }
1963        } else {
1964            0.0
1965        };
1966
1967        let result = if n >= step { 1.0 } else { 0.0 };
1968        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
1969            result,
1970        )))
1971    }
1972}
1973
1974/* ─────────────────────────── Error Function ──────────────────────────── */
1975
1976/// Approximation of the error function erf(x)
1977/// Uses the approximation: erf(x) = 1 - (a1*t + a2*t^2 + a3*t^3 + a4*t^4 + a5*t^5) * exp(-x^2)
1978/// High-precision error function using Cody's rational approximation
1979/// Achieves precision of about 1e-15 (double precision)
1980#[allow(clippy::excessive_precision)]
1981fn erf_approx(x: f64) -> f64 {
1982    let ax = x.abs();
1983
1984    // For small x, use series expansion
1985    if ax < 0.5 {
1986        // Coefficients for erf(x) = x * P(x^2) / Q(x^2)
1987        const P: [f64; 5] = [
1988            3.20937758913846947e+03,
1989            3.77485237685302021e+02,
1990            1.13864154151050156e+02,
1991            3.16112374387056560e+00,
1992            1.85777706184603153e-01,
1993        ];
1994        const Q: [f64; 5] = [
1995            2.84423748127893300e+03,
1996            1.28261652607737228e+03,
1997            2.44024637934444173e+02,
1998            2.36012909523441209e+01,
1999            1.00000000000000000e+00,
2000        ];
2001
2002        let x2 = x * x;
2003        let p_val = P[4];
2004        let p_val = p_val * x2 + P[3];
2005        let p_val = p_val * x2 + P[2];
2006        let p_val = p_val * x2 + P[1];
2007        let p_val = p_val * x2 + P[0];
2008
2009        let q_val = Q[4];
2010        let q_val = q_val * x2 + Q[3];
2011        let q_val = q_val * x2 + Q[2];
2012        let q_val = q_val * x2 + Q[1];
2013        let q_val = q_val * x2 + Q[0];
2014
2015        return x * p_val / q_val;
2016    }
2017
2018    // For x in [0.5, 4], use erfc approximation and compute erf = 1 - erfc
2019    if ax < 4.0 {
2020        let erfc_val = erfc_mid(ax);
2021        return if x > 0.0 {
2022            1.0 - erfc_val
2023        } else {
2024            erfc_val - 1.0
2025        };
2026    }
2027
2028    // For large x, erf(x) ≈ ±1
2029    let erfc_val = erfc_large(ax);
2030    if x > 0.0 {
2031        1.0 - erfc_val
2032    } else {
2033        erfc_val - 1.0
2034    }
2035}
2036
2037/// erfc for x in [0.5, 4]
2038#[allow(clippy::excessive_precision)]
2039fn erfc_mid(x: f64) -> f64 {
2040    const P: [f64; 9] = [
2041        1.23033935479799725e+03,
2042        2.05107837782607147e+03,
2043        1.71204761263407058e+03,
2044        8.81952221241769090e+02,
2045        2.98635138197400131e+02,
2046        6.61191906371416295e+01,
2047        8.88314979438837594e+00,
2048        5.64188496988670089e-01,
2049        2.15311535474403846e-08,
2050    ];
2051    const Q: [f64; 9] = [
2052        1.23033935480374942e+03,
2053        3.43936767414372164e+03,
2054        4.36261909014324716e+03,
2055        3.29079923573345963e+03,
2056        1.62138957456669019e+03,
2057        5.37181101862009858e+02,
2058        1.17693950891312499e+02,
2059        1.57449261107098347e+01,
2060        1.00000000000000000e+00,
2061    ];
2062
2063    let p_val = P[8];
2064    let p_val = p_val * x + P[7];
2065    let p_val = p_val * x + P[6];
2066    let p_val = p_val * x + P[5];
2067    let p_val = p_val * x + P[4];
2068    let p_val = p_val * x + P[3];
2069    let p_val = p_val * x + P[2];
2070    let p_val = p_val * x + P[1];
2071    let p_val = p_val * x + P[0];
2072
2073    let q_val = Q[8];
2074    let q_val = q_val * x + Q[7];
2075    let q_val = q_val * x + Q[6];
2076    let q_val = q_val * x + Q[5];
2077    let q_val = q_val * x + Q[4];
2078    let q_val = q_val * x + Q[3];
2079    let q_val = q_val * x + Q[2];
2080    let q_val = q_val * x + Q[1];
2081    let q_val = q_val * x + Q[0];
2082
2083    (-x * x).exp() * p_val / q_val
2084}
2085
2086/// erfc for x >= 4
2087#[allow(clippy::excessive_precision)]
2088fn erfc_large(x: f64) -> f64 {
2089    const P: [f64; 6] = [
2090        6.58749161529837803e-04,
2091        1.60837851487422766e-02,
2092        1.25781726111229246e-01,
2093        3.60344899949804439e-01,
2094        3.05326634961232344e-01,
2095        1.63153871373020978e-02,
2096    ];
2097    const Q: [f64; 6] = [
2098        2.33520497626869185e-03,
2099        6.05183413124413191e-02,
2100        5.27905102951428412e-01,
2101        1.87295284992346047e+00,
2102        2.56852019228982242e+00,
2103        1.00000000000000000e+00,
2104    ];
2105
2106    let x2 = x * x;
2107    let inv_x2 = 1.0 / x2;
2108
2109    let p_val = P[5];
2110    let p_val = p_val * inv_x2 + P[4];
2111    let p_val = p_val * inv_x2 + P[3];
2112    let p_val = p_val * inv_x2 + P[2];
2113    let p_val = p_val * inv_x2 + P[1];
2114    let p_val = p_val * inv_x2 + P[0];
2115
2116    let q_val = Q[5];
2117    let q_val = q_val * inv_x2 + Q[4];
2118    let q_val = q_val * inv_x2 + Q[3];
2119    let q_val = q_val * inv_x2 + Q[2];
2120    let q_val = q_val * inv_x2 + Q[1];
2121    let q_val = q_val * inv_x2 + Q[0];
2122
2123    // 1/sqrt(pi) = 0.5641895835477563
2124    const FRAC_1_SQRT_PI: f64 = 0.5641895835477563;
2125    (-x2).exp() / x * (FRAC_1_SQRT_PI + inv_x2 * p_val / q_val)
2126}
2127
2128/// Direct erfc computation for ERFC function
2129fn erfc_direct(x: f64) -> f64 {
2130    if x < 0.0 {
2131        return 2.0 - erfc_direct(-x);
2132    }
2133    if x < 0.5 {
2134        return 1.0 - erf_approx(x);
2135    }
2136    if x < 4.0 {
2137        return erfc_mid(x);
2138    }
2139    erfc_large(x)
2140}
2141
2142/// Returns the Gaussian error function over one bound or between two bounds.
2143///
2144/// With one argument it returns `erf(x)`; with two it returns `erf(upper) - erf(lower)`.
2145///
2146/// # Remarks
2147/// - Inputs are numerically coerced.
2148/// - A second argument switches the function to interval mode.
2149/// - Results are approximate floating-point values.
2150///
2151/// # Examples
2152/// ```yaml,sandbox
2153/// title: "Single-bound ERF"
2154/// formula: "=ERF(1)"
2155/// expected: 0.8427007929497149
2156/// ```
2157///
2158/// ```yaml,sandbox
2159/// title: "Interval ERF"
2160/// formula: "=ERF(0,1)"
2161/// expected: 0.8427007929497149
2162/// ```
2163/// ```yaml,docs
2164/// related:
2165///   - ERFC
2166///   - ERF.PRECISE
2167/// faq:
2168///   - q: "How does two-argument `ERF` work?"
2169///     a: "`ERF(lower, upper)` returns `erf(upper) - erf(lower)`, i.e., an interval difference rather than a single-bound value."
2170/// ```
2171#[derive(Debug)]
2172pub struct ErfFn;
2173/// [formualizer-docgen:schema:start]
2174/// Name: ERF
2175/// Type: ErfFn
2176/// Min args: 1
2177/// Max args: variadic
2178/// Variadic: true
2179/// Signature: ERF(arg1: number@scalar, arg2...: number@scalar)
2180/// Arg schema: arg1{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}; arg2{kinds=number,required=true,shape=scalar,by_ref=false,coercion=NumberLenientText,max=None,repeating=None,default=false}
2181/// Caps: PURE
2182/// [formualizer-docgen:schema:end]
2183impl Function for ErfFn {
2184    func_caps!(PURE);
2185    fn name(&self) -> &'static str {
2186        "ERF"
2187    }
2188    fn min_args(&self) -> usize {
2189        1
2190    }
2191    fn variadic(&self) -> bool {
2192        true
2193    }
2194    fn arg_schema(&self) -> &'static [ArgSchema] {
2195        &ARG_NUM_LENIENT_TWO[..]
2196    }
2197    fn eval<'a, 'b, 'c>(
2198        &self,
2199        args: &'c [ArgumentHandle<'a, 'b>],
2200        _ctx: &dyn FunctionContext<'b>,
2201    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2202        let lower = match args[0].value()?.into_literal() {
2203            LiteralValue::Error(e) => {
2204                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2205            }
2206            other => coerce_num(&other)?,
2207        };
2208
2209        let result = if args.len() > 1 {
2210            // ERF(lower, upper) = erf(upper) - erf(lower)
2211            let upper = match args[1].value()?.into_literal() {
2212                LiteralValue::Error(e) => {
2213                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2214                }
2215                other => coerce_num(&other)?,
2216            };
2217            erf_approx(upper) - erf_approx(lower)
2218        } else {
2219            // ERF(x) = erf(x)
2220            erf_approx(lower)
2221        };
2222
2223        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
2224            result,
2225        )))
2226    }
2227}
2228
2229/// Returns the complementary error function of a number.
2230///
2231/// `ERFC(x)` is equivalent to `1 - ERF(x)`.
2232///
2233/// # Remarks
2234/// - Input is numerically coerced.
2235/// - Results are approximate floating-point values.
2236/// - Propagates input errors.
2237///
2238/// # Examples
2239/// ```yaml,sandbox
2240/// title: "Complement at one"
2241/// formula: "=ERFC(1)"
2242/// expected: 0.1572992070502851
2243/// ```
2244///
2245/// ```yaml,sandbox
2246/// title: "Complement at zero"
2247/// formula: "=ERFC(0)"
2248/// expected: 1
2249/// ```
2250/// ```yaml,docs
2251/// related:
2252///   - ERF
2253///   - ERF.PRECISE
2254/// faq:
2255///   - q: "Is `ERFC(x)` equivalent to `1-ERF(x)` here?"
2256///     a: "Yes. It computes the complementary error function and matches `1 - erf(x)` behavior."
2257/// ```
2258#[derive(Debug)]
2259pub struct ErfcFn;
2260/// [formualizer-docgen:schema:start]
2261/// Name: ERFC
2262/// Type: ErfcFn
2263/// Min args: 1
2264/// Max args: 1
2265/// Variadic: false
2266/// Signature: ERFC(arg1: any@scalar)
2267/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2268/// Caps: PURE
2269/// [formualizer-docgen:schema:end]
2270impl Function for ErfcFn {
2271    func_caps!(PURE);
2272    fn name(&self) -> &'static str {
2273        "ERFC"
2274    }
2275    fn min_args(&self) -> usize {
2276        1
2277    }
2278    fn arg_schema(&self) -> &'static [ArgSchema] {
2279        &ARG_ANY_ONE[..]
2280    }
2281    fn eval<'a, 'b, 'c>(
2282        &self,
2283        args: &'c [ArgumentHandle<'a, 'b>],
2284        _ctx: &dyn FunctionContext<'b>,
2285    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2286        let x = match args[0].value()?.into_literal() {
2287            LiteralValue::Error(e) => {
2288                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2289            }
2290            other => coerce_num(&other)?,
2291        };
2292
2293        let result = erfc_direct(x);
2294        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
2295            result,
2296        )))
2297    }
2298}
2299
2300/// Returns the error function of a number.
2301///
2302/// This is the one-argument precise variant of `ERF`.
2303///
2304/// # Remarks
2305/// - Input is numerically coerced.
2306/// - Equivalent to `ERF(x)` in single-argument mode.
2307/// - Results are approximate floating-point values.
2308///
2309/// # Examples
2310/// ```yaml,sandbox
2311/// title: "Positive input"
2312/// formula: "=ERF.PRECISE(1)"
2313/// expected: 0.8427007929497149
2314/// ```
2315///
2316/// ```yaml,sandbox
2317/// title: "Negative input"
2318/// formula: "=ERF.PRECISE(-1)"
2319/// expected: -0.8427007929497149
2320/// ```
2321/// ```yaml,docs
2322/// related:
2323///   - ERF
2324///   - ERFC
2325/// faq:
2326///   - q: "How is `ERF.PRECISE` different from `ERF`?"
2327///     a: "`ERF.PRECISE` is the one-argument form only; numerically it matches `ERF(x)` for single input mode."
2328/// ```
2329#[derive(Debug)]
2330pub struct ErfPreciseFn;
2331/// [formualizer-docgen:schema:start]
2332/// Name: ERF.PRECISE
2333/// Type: ErfPreciseFn
2334/// Min args: 1
2335/// Max args: 1
2336/// Variadic: false
2337/// Signature: ERF.PRECISE(arg1: any@scalar)
2338/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2339/// Caps: PURE
2340/// [formualizer-docgen:schema:end]
2341impl Function for ErfPreciseFn {
2342    func_caps!(PURE);
2343    fn name(&self) -> &'static str {
2344        "ERF.PRECISE"
2345    }
2346    fn min_args(&self) -> usize {
2347        1
2348    }
2349    fn arg_schema(&self) -> &'static [ArgSchema] {
2350        &ARG_ANY_ONE[..]
2351    }
2352    fn eval<'a, 'b, 'c>(
2353        &self,
2354        args: &'c [ArgumentHandle<'a, 'b>],
2355        _ctx: &dyn FunctionContext<'b>,
2356    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2357        let x = match args[0].value()?.into_literal() {
2358            LiteralValue::Error(e) => {
2359                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2360            }
2361            other => coerce_num(&other)?,
2362        };
2363
2364        let result = erf_approx(x);
2365        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
2366            result,
2367        )))
2368    }
2369}
2370
2371/* ─────────────────────────── Complex Number Functions ──────────────────────────── */
2372
2373/// Parse a complex number string like "3+4i", "3-4i", "5i", "3", "-2j", etc.
2374/// Returns (real, imaginary, suffix) where suffix is 'i' or 'j'
2375fn parse_complex(s: &str) -> Result<(f64, f64, char), ExcelError> {
2376    let s = s.trim();
2377    if s.is_empty() {
2378        return Err(ExcelError::new_num());
2379    }
2380
2381    // Determine the suffix (i or j)
2382    let suffix = if s.ends_with('i') || s.ends_with('I') {
2383        'i'
2384    } else if s.ends_with('j') || s.ends_with('J') {
2385        'j'
2386    } else {
2387        // No imaginary suffix - must be purely real
2388        let real: f64 = s.parse().map_err(|_| ExcelError::new_num())?;
2389        return Ok((real, 0.0, 'i'));
2390    };
2391
2392    // Remove the suffix for parsing
2393    let s = &s[..s.len() - 1];
2394
2395    // Handle pure imaginary cases like "i", "-i", "4i"
2396    if s.is_empty() || s == "+" {
2397        return Ok((0.0, 1.0, suffix));
2398    }
2399    if s == "-" {
2400        return Ok((0.0, -1.0, suffix));
2401    }
2402
2403    // Find the last + or - that separates real and imaginary parts
2404    // We need to skip the first character (could be a sign) and find operators
2405    let mut split_pos = None;
2406    let bytes = s.as_bytes();
2407
2408    for i in (1..bytes.len()).rev() {
2409        let c = bytes[i] as char;
2410        if c == '+' || c == '-' {
2411            // Make sure this isn't part of an exponent (e.g., "1e-5")
2412            if i > 0 {
2413                let prev = bytes[i - 1] as char;
2414                if prev == 'e' || prev == 'E' {
2415                    continue;
2416                }
2417            }
2418            split_pos = Some(i);
2419            break;
2420        }
2421    }
2422
2423    match split_pos {
2424        Some(pos) => {
2425            // We have both real and imaginary parts
2426            let real_str = &s[..pos];
2427            let imag_str = &s[pos..];
2428
2429            let real: f64 = if real_str.is_empty() {
2430                0.0
2431            } else {
2432                real_str.parse().map_err(|_| ExcelError::new_num())?
2433            };
2434
2435            // Handle imaginary part (could be "+", "-", "+5", "-5", etc.)
2436            let imag: f64 = if imag_str == "+" {
2437                1.0
2438            } else if imag_str == "-" {
2439                -1.0
2440            } else {
2441                imag_str.parse().map_err(|_| ExcelError::new_num())?
2442            };
2443
2444            Ok((real, imag, suffix))
2445        }
2446        None => {
2447            // Pure imaginary number (no real part), e.g., "5" (before suffix was removed)
2448            let imag: f64 = s.parse().map_err(|_| ExcelError::new_num())?;
2449            Ok((0.0, imag, suffix))
2450        }
2451    }
2452}
2453
2454/// Clean up floating point noise by rounding values very close to integers
2455fn clean_float(val: f64) -> f64 {
2456    let rounded = val.round();
2457    if (val - rounded).abs() < 1e-10 {
2458        rounded
2459    } else {
2460        val
2461    }
2462}
2463
2464/// Format a complex number as a string
2465fn format_complex(real: f64, imag: f64, suffix: char) -> String {
2466    // Clean up floating point noise
2467    let real = clean_float(real);
2468    let imag = clean_float(imag);
2469
2470    // Handle special cases for cleaner output
2471    let real_is_zero = real.abs() < 1e-15;
2472    let imag_is_zero = imag.abs() < 1e-15;
2473
2474    if real_is_zero && imag_is_zero {
2475        return "0".to_string();
2476    }
2477
2478    if imag_is_zero {
2479        // Purely real
2480        if real == real.trunc() && real.abs() < 1e15 {
2481            return format!("{}", real as i64);
2482        }
2483        return format!("{}", real);
2484    }
2485
2486    if real_is_zero {
2487        // Purely imaginary
2488        if (imag - 1.0).abs() < 1e-15 {
2489            return format!("{}", suffix);
2490        }
2491        if (imag + 1.0).abs() < 1e-15 {
2492            return format!("-{}", suffix);
2493        }
2494        if imag == imag.trunc() && imag.abs() < 1e15 {
2495            return format!("{}{}", imag as i64, suffix);
2496        }
2497        return format!("{}{}", imag, suffix);
2498    }
2499
2500    // Both parts are non-zero
2501    let real_str = if real == real.trunc() && real.abs() < 1e15 {
2502        format!("{}", real as i64)
2503    } else {
2504        format!("{}", real)
2505    };
2506
2507    let imag_str = if (imag - 1.0).abs() < 1e-15 {
2508        format!("+{}", suffix)
2509    } else if (imag + 1.0).abs() < 1e-15 {
2510        format!("-{}", suffix)
2511    } else if imag > 0.0 {
2512        if imag == imag.trunc() && imag.abs() < 1e15 {
2513            format!("+{}{}", imag as i64, suffix)
2514        } else {
2515            format!("+{}{}", imag, suffix)
2516        }
2517    } else if imag == imag.trunc() && imag.abs() < 1e15 {
2518        format!("{}{}", imag as i64, suffix)
2519    } else {
2520        format!("{}{}", imag, suffix)
2521    };
2522
2523    format!("{}{}", real_str, imag_str)
2524}
2525
2526/// Coerce a LiteralValue to a complex number string
2527fn coerce_complex_str(v: &LiteralValue) -> Result<String, ExcelError> {
2528    match v {
2529        LiteralValue::Text(s) => Ok(s.clone()),
2530        LiteralValue::Int(i) => Ok(i.to_string()),
2531        LiteralValue::Number(n) => Ok(n.to_string()),
2532        LiteralValue::Error(e) => Err(e.clone()),
2533        _ => Err(ExcelError::new_value()),
2534    }
2535}
2536
2537/// Three-argument schema for COMPLEX function
2538static ARG_COMPLEX_THREE: std::sync::LazyLock<Vec<ArgSchema>> =
2539    std::sync::LazyLock::new(|| vec![ArgSchema::any(), ArgSchema::any(), ArgSchema::any()]);
2540
2541/// Builds a complex number text value from real and imaginary coefficients.
2542///
2543/// Returns canonical text such as `3+4i` or `-2j`.
2544///
2545/// # Remarks
2546/// - `real_num` and `i_num` are numerically coerced.
2547/// - `suffix` may be `"i"`, `"j"`, empty text, or omitted; empty/omitted defaults to `i`.
2548/// - Any other suffix returns `#VALUE!`.
2549///
2550/// # Examples
2551/// ```yaml,sandbox
2552/// title: "Build with default suffix"
2553/// formula: "=COMPLEX(3,4)"
2554/// expected: "3+4i"
2555/// ```
2556///
2557/// ```yaml,sandbox
2558/// title: "Build with j suffix"
2559/// formula: "=COMPLEX(0,-1,\"j\")"
2560/// expected: "-j"
2561/// ```
2562/// ```yaml,docs
2563/// related:
2564///   - IMREAL
2565///   - IMAGINARY
2566///   - IMSUM
2567/// faq:
2568///   - q: "Which suffix values are valid in `COMPLEX`?"
2569///     a: "Only suffixes i or j are accepted (empty or omitted defaults to i); other suffix strings return `#VALUE!`."
2570/// ```
2571#[derive(Debug)]
2572pub struct ComplexFn;
2573/// [formualizer-docgen:schema:start]
2574/// Name: COMPLEX
2575/// Type: ComplexFn
2576/// Min args: 2
2577/// Max args: variadic
2578/// Variadic: true
2579/// Signature: COMPLEX(arg1: any@scalar, arg2: any@scalar, arg3...: any@scalar)
2580/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg3{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2581/// Caps: PURE
2582/// [formualizer-docgen:schema:end]
2583impl Function for ComplexFn {
2584    func_caps!(PURE);
2585    fn name(&self) -> &'static str {
2586        "COMPLEX"
2587    }
2588    fn min_args(&self) -> usize {
2589        2
2590    }
2591    fn variadic(&self) -> bool {
2592        true
2593    }
2594    fn arg_schema(&self) -> &'static [ArgSchema] {
2595        &ARG_COMPLEX_THREE[..]
2596    }
2597    fn eval<'a, 'b, 'c>(
2598        &self,
2599        args: &'c [ArgumentHandle<'a, 'b>],
2600        _ctx: &dyn FunctionContext<'b>,
2601    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2602        let real = match args[0].value()?.into_literal() {
2603            LiteralValue::Error(e) => {
2604                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2605            }
2606            other => coerce_num(&other)?,
2607        };
2608
2609        let imag = match args[1].value()?.into_literal() {
2610            LiteralValue::Error(e) => {
2611                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2612            }
2613            other => coerce_num(&other)?,
2614        };
2615
2616        let suffix = if args.len() > 2 {
2617            match args[2].value()?.into_literal() {
2618                LiteralValue::Error(e) => {
2619                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2620                }
2621                LiteralValue::Text(s) => {
2622                    let s = s.to_lowercase();
2623                    if s == "i" {
2624                        'i'
2625                    } else if s == "j" {
2626                        'j'
2627                    } else if s.is_empty() {
2628                        'i' // Default to 'i' for empty string
2629                    } else {
2630                        return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2631                            ExcelError::new_value(),
2632                        )));
2633                    }
2634                }
2635                LiteralValue::Empty => 'i',
2636                _ => {
2637                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2638                        ExcelError::new_value(),
2639                    )));
2640                }
2641            }
2642        } else {
2643            'i'
2644        };
2645
2646        let result = format_complex(real, imag, suffix);
2647        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
2648    }
2649}
2650
2651/// Returns the real coefficient of a complex number.
2652///
2653/// Accepts complex text (for example `a+bi`) or numeric values.
2654///
2655/// # Remarks
2656/// - Inputs are coerced to complex-number text before parsing.
2657/// - Purely imaginary values return `0`.
2658/// - Invalid complex text returns `#NUM!`.
2659///
2660/// # Examples
2661/// ```yaml,sandbox
2662/// title: "Real part from a+bi"
2663/// formula: "=IMREAL(\"3+4i\")"
2664/// expected: 3
2665/// ```
2666///
2667/// ```yaml,sandbox
2668/// title: "Real part of pure imaginary"
2669/// formula: "=IMREAL(\"5j\")"
2670/// expected: 0
2671/// ```
2672/// ```yaml,docs
2673/// related:
2674///   - IMAGINARY
2675///   - COMPLEX
2676///   - IMABS
2677/// faq:
2678///   - q: "What does `IMREAL` return for a purely imaginary input?"
2679///     a: "It returns `0` because the real coefficient is zero."
2680/// ```
2681#[derive(Debug)]
2682pub struct ImRealFn;
2683/// [formualizer-docgen:schema:start]
2684/// Name: IMREAL
2685/// Type: ImRealFn
2686/// Min args: 1
2687/// Max args: 1
2688/// Variadic: false
2689/// Signature: IMREAL(arg1: any@scalar)
2690/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2691/// Caps: PURE
2692/// [formualizer-docgen:schema:end]
2693impl Function for ImRealFn {
2694    func_caps!(PURE);
2695    fn name(&self) -> &'static str {
2696        "IMREAL"
2697    }
2698    fn min_args(&self) -> usize {
2699        1
2700    }
2701    fn arg_schema(&self) -> &'static [ArgSchema] {
2702        &ARG_ANY_ONE[..]
2703    }
2704    fn eval<'a, 'b, 'c>(
2705        &self,
2706        args: &'c [ArgumentHandle<'a, 'b>],
2707        _ctx: &dyn FunctionContext<'b>,
2708    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2709        let inumber = match args[0].value()?.into_literal() {
2710            LiteralValue::Error(e) => {
2711                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2712            }
2713            other => match coerce_complex_str(&other) {
2714                Ok(s) => s,
2715                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2716            },
2717        };
2718
2719        let (real, _, _) = match parse_complex(&inumber) {
2720            Ok(c) => c,
2721            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2722        };
2723
2724        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(real)))
2725    }
2726}
2727
2728/// Returns the imaginary coefficient of a complex number.
2729///
2730/// Accepts complex text (for example `a+bi`) or numeric values.
2731///
2732/// # Remarks
2733/// - Inputs are coerced to complex-number text before parsing.
2734/// - Purely real values return `0`.
2735/// - Invalid complex text returns `#NUM!`.
2736///
2737/// # Examples
2738/// ```yaml,sandbox
2739/// title: "Imaginary part from a+bi"
2740/// formula: "=IMAGINARY(\"3+4i\")"
2741/// expected: 4
2742/// ```
2743///
2744/// ```yaml,sandbox
2745/// title: "Imaginary part with j suffix"
2746/// formula: "=IMAGINARY(\"-2j\")"
2747/// expected: -2
2748/// ```
2749/// ```yaml,docs
2750/// related:
2751///   - IMREAL
2752///   - COMPLEX
2753///   - IMABS
2754/// faq:
2755///   - q: "What does `IMAGINARY` return for a real-only input?"
2756///     a: "It returns `0` because there is no imaginary component."
2757/// ```
2758#[derive(Debug)]
2759pub struct ImaginaryFn;
2760/// [formualizer-docgen:schema:start]
2761/// Name: IMAGINARY
2762/// Type: ImaginaryFn
2763/// Min args: 1
2764/// Max args: 1
2765/// Variadic: false
2766/// Signature: IMAGINARY(arg1: any@scalar)
2767/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2768/// Caps: PURE
2769/// [formualizer-docgen:schema:end]
2770impl Function for ImaginaryFn {
2771    func_caps!(PURE);
2772    fn name(&self) -> &'static str {
2773        "IMAGINARY"
2774    }
2775    fn min_args(&self) -> usize {
2776        1
2777    }
2778    fn arg_schema(&self) -> &'static [ArgSchema] {
2779        &ARG_ANY_ONE[..]
2780    }
2781    fn eval<'a, 'b, 'c>(
2782        &self,
2783        args: &'c [ArgumentHandle<'a, 'b>],
2784        _ctx: &dyn FunctionContext<'b>,
2785    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2786        let inumber = match args[0].value()?.into_literal() {
2787            LiteralValue::Error(e) => {
2788                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2789            }
2790            other => match coerce_complex_str(&other) {
2791                Ok(s) => s,
2792                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2793            },
2794        };
2795
2796        let (_, imag, _) = match parse_complex(&inumber) {
2797            Ok(c) => c,
2798            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2799        };
2800
2801        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(imag)))
2802    }
2803}
2804
2805/// Returns the modulus (absolute value) of a complex number.
2806///
2807/// Computes `sqrt(real^2 + imaginary^2)`.
2808///
2809/// # Remarks
2810/// - Inputs are coerced to complex-number text before parsing.
2811/// - Returns a non-negative real number.
2812/// - Invalid complex text returns `#NUM!`.
2813///
2814/// # Examples
2815/// ```yaml,sandbox
2816/// title: "3-4-5 triangle modulus"
2817/// formula: "=IMABS(\"3+4i\")"
2818/// expected: 5
2819/// ```
2820///
2821/// ```yaml,sandbox
2822/// title: "Purely real input"
2823/// formula: "=IMABS(\"5\")"
2824/// expected: 5
2825/// ```
2826/// ```yaml,docs
2827/// related:
2828///   - IMREAL
2829///   - IMAGINARY
2830///   - IMARGUMENT
2831/// faq:
2832///   - q: "Can `IMABS` return a negative result?"
2833///     a: "No. It computes the modulus `sqrt(a^2+b^2)`, which is always non-negative."
2834/// ```
2835#[derive(Debug)]
2836pub struct ImAbsFn;
2837/// [formualizer-docgen:schema:start]
2838/// Name: IMABS
2839/// Type: ImAbsFn
2840/// Min args: 1
2841/// Max args: 1
2842/// Variadic: false
2843/// Signature: IMABS(arg1: any@scalar)
2844/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2845/// Caps: PURE
2846/// [formualizer-docgen:schema:end]
2847impl Function for ImAbsFn {
2848    func_caps!(PURE);
2849    fn name(&self) -> &'static str {
2850        "IMABS"
2851    }
2852    fn min_args(&self) -> usize {
2853        1
2854    }
2855    fn arg_schema(&self) -> &'static [ArgSchema] {
2856        &ARG_ANY_ONE[..]
2857    }
2858    fn eval<'a, 'b, 'c>(
2859        &self,
2860        args: &'c [ArgumentHandle<'a, 'b>],
2861        _ctx: &dyn FunctionContext<'b>,
2862    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2863        let inumber = match args[0].value()?.into_literal() {
2864            LiteralValue::Error(e) => {
2865                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2866            }
2867            other => match coerce_complex_str(&other) {
2868                Ok(s) => s,
2869                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2870            },
2871        };
2872
2873        let (real, imag, _) = match parse_complex(&inumber) {
2874            Ok(c) => c,
2875            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2876        };
2877
2878        let abs = (real * real + imag * imag).sqrt();
2879        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(abs)))
2880    }
2881}
2882
2883/// Returns the argument (angle in radians) of a complex number.
2884///
2885/// The angle is measured from the positive real axis.
2886///
2887/// # Remarks
2888/// - Inputs are coerced to complex-number text before parsing.
2889/// - Returns `#DIV/0!` for `0+0i`, where the angle is undefined.
2890/// - Invalid complex text returns `#NUM!`.
2891///
2892/// # Examples
2893/// ```yaml,sandbox
2894/// title: "First-quadrant angle"
2895/// formula: "=IMARGUMENT(\"1+i\")"
2896/// expected: 0.7853981633974483
2897/// ```
2898///
2899/// ```yaml,sandbox
2900/// title: "Negative real axis"
2901/// formula: "=IMARGUMENT(\"-1\")"
2902/// expected: 3.141592653589793
2903/// ```
2904/// ```yaml,docs
2905/// related:
2906///   - IMABS
2907///   - IMLN
2908///   - IMSQRT
2909/// faq:
2910///   - q: "Why does `IMARGUMENT(0)` return `#DIV/0!`?"
2911///     a: "The argument (angle) of `0+0i` is undefined, so the function returns `#DIV/0!`."
2912/// ```
2913#[derive(Debug)]
2914pub struct ImArgumentFn;
2915/// [formualizer-docgen:schema:start]
2916/// Name: IMARGUMENT
2917/// Type: ImArgumentFn
2918/// Min args: 1
2919/// Max args: 1
2920/// Variadic: false
2921/// Signature: IMARGUMENT(arg1: any@scalar)
2922/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
2923/// Caps: PURE
2924/// [formualizer-docgen:schema:end]
2925impl Function for ImArgumentFn {
2926    func_caps!(PURE);
2927    fn name(&self) -> &'static str {
2928        "IMARGUMENT"
2929    }
2930    fn min_args(&self) -> usize {
2931        1
2932    }
2933    fn arg_schema(&self) -> &'static [ArgSchema] {
2934        &ARG_ANY_ONE[..]
2935    }
2936    fn eval<'a, 'b, 'c>(
2937        &self,
2938        args: &'c [ArgumentHandle<'a, 'b>],
2939        _ctx: &dyn FunctionContext<'b>,
2940    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
2941        let inumber = match args[0].value()?.into_literal() {
2942            LiteralValue::Error(e) => {
2943                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
2944            }
2945            other => match coerce_complex_str(&other) {
2946                Ok(s) => s,
2947                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2948            },
2949        };
2950
2951        let (real, imag, _) = match parse_complex(&inumber) {
2952            Ok(c) => c,
2953            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
2954        };
2955
2956        // Excel returns #DIV/0! for IMARGUMENT(0)
2957        if real.abs() < 1e-15 && imag.abs() < 1e-15 {
2958            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2959                ExcelError::new_div(),
2960            )));
2961        }
2962
2963        let arg = imag.atan2(real);
2964        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(arg)))
2965    }
2966}
2967
2968/// Returns the complex conjugate of a complex number.
2969///
2970/// Negates the imaginary coefficient and keeps the real coefficient unchanged.
2971///
2972/// # Remarks
2973/// - Inputs are coerced to complex-number text before parsing.
2974/// - Preserves the original suffix style (`i` or `j`) when possible.
2975/// - Invalid complex text returns `#NUM!`.
2976///
2977/// # Examples
2978/// ```yaml,sandbox
2979/// title: "Conjugate with i suffix"
2980/// formula: "=IMCONJUGATE(\"3+4i\")"
2981/// expected: "3-4i"
2982/// ```
2983///
2984/// ```yaml,sandbox
2985/// title: "Conjugate with j suffix"
2986/// formula: "=IMCONJUGATE(\"-2j\")"
2987/// expected: "2j"
2988/// ```
2989/// ```yaml,docs
2990/// related:
2991///   - IMSUB
2992///   - IMPRODUCT
2993///   - IMDIV
2994/// faq:
2995///   - q: "Does `IMCONJUGATE` keep the `i`/`j` suffix style?"
2996///     a: "Yes. It negates only the imaginary coefficient and preserves the parsed suffix form."
2997/// ```
2998#[derive(Debug)]
2999pub struct ImConjugateFn;
3000/// [formualizer-docgen:schema:start]
3001/// Name: IMCONJUGATE
3002/// Type: ImConjugateFn
3003/// Min args: 1
3004/// Max args: 1
3005/// Variadic: false
3006/// Signature: IMCONJUGATE(arg1: any@scalar)
3007/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3008/// Caps: PURE
3009/// [formualizer-docgen:schema:end]
3010impl Function for ImConjugateFn {
3011    func_caps!(PURE);
3012    fn name(&self) -> &'static str {
3013        "IMCONJUGATE"
3014    }
3015    fn min_args(&self) -> usize {
3016        1
3017    }
3018    fn arg_schema(&self) -> &'static [ArgSchema] {
3019        &ARG_ANY_ONE[..]
3020    }
3021    fn eval<'a, 'b, 'c>(
3022        &self,
3023        args: &'c [ArgumentHandle<'a, 'b>],
3024        _ctx: &dyn FunctionContext<'b>,
3025    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3026        let inumber = match args[0].value()?.into_literal() {
3027            LiteralValue::Error(e) => {
3028                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3029            }
3030            other => match coerce_complex_str(&other) {
3031                Ok(s) => s,
3032                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3033            },
3034        };
3035
3036        let (real, imag, suffix) = match parse_complex(&inumber) {
3037            Ok(c) => c,
3038            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3039        };
3040
3041        let result = format_complex(real, -imag, suffix);
3042        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3043    }
3044}
3045
3046/// Helper to check if two complex numbers have compatible suffixes
3047fn check_suffix_compatibility(s1: char, s2: char) -> Result<char, ExcelError> {
3048    // If both have the same suffix, use it
3049    // If one is from a purely real number (default 'i'), use the other's suffix
3050    // Excel doesn't allow mixing 'i' and 'j' when both are explicit
3051    if s1 == s2 {
3052        Ok(s1)
3053    } else {
3054        // For simplicity, treat 'i' as the default and allow mixed
3055        // In strict Excel mode, this would error
3056        Ok(s1)
3057    }
3058}
3059
3060/// Returns the sum of one or more complex numbers.
3061///
3062/// Adds real parts together and imaginary parts together.
3063///
3064/// # Remarks
3065/// - Each argument is coerced to complex-number text before parsing.
3066/// - Accepts any number of arguments from one upward.
3067/// - Invalid complex text returns `#NUM!`.
3068///
3069/// # Examples
3070/// ```yaml,sandbox
3071/// title: "Add multiple complex values"
3072/// formula: "=IMSUM(\"3+4i\",\"1-2i\",\"5\")"
3073/// expected: "9+2i"
3074/// ```
3075///
3076/// ```yaml,sandbox
3077/// title: "Add j-suffix values"
3078/// formula: "=IMSUM(\"2j\",\"-j\")"
3079/// expected: "j"
3080/// ```
3081/// ```yaml,docs
3082/// related:
3083///   - IMSUB
3084///   - IMPRODUCT
3085///   - COMPLEX
3086/// faq:
3087///   - q: "Can `IMSUM` take more than two arguments?"
3088///     a: "Yes. It is variadic and sums all provided complex arguments in sequence."
3089/// ```
3090#[derive(Debug)]
3091pub struct ImSumFn;
3092/// [formualizer-docgen:schema:start]
3093/// Name: IMSUM
3094/// Type: ImSumFn
3095/// Min args: 1
3096/// Max args: variadic
3097/// Variadic: true
3098/// Signature: IMSUM(arg1...: any@scalar)
3099/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3100/// Caps: PURE
3101/// [formualizer-docgen:schema:end]
3102impl Function for ImSumFn {
3103    func_caps!(PURE);
3104    fn name(&self) -> &'static str {
3105        "IMSUM"
3106    }
3107    fn min_args(&self) -> usize {
3108        1
3109    }
3110    fn variadic(&self) -> bool {
3111        true
3112    }
3113    fn arg_schema(&self) -> &'static [ArgSchema] {
3114        &ARG_ANY_ONE[..]
3115    }
3116    fn eval<'a, 'b, 'c>(
3117        &self,
3118        args: &'c [ArgumentHandle<'a, 'b>],
3119        _ctx: &dyn FunctionContext<'b>,
3120    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3121        let mut sum_real = 0.0;
3122        let mut sum_imag = 0.0;
3123        let mut result_suffix = 'i';
3124        let mut first = true;
3125
3126        for arg in args {
3127            let inumber = match arg.value()?.into_literal() {
3128                LiteralValue::Error(e) => {
3129                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3130                }
3131                other => match coerce_complex_str(&other) {
3132                    Ok(s) => s,
3133                    Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3134                },
3135            };
3136
3137            let (real, imag, suffix) = match parse_complex(&inumber) {
3138                Ok(c) => c,
3139                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3140            };
3141
3142            if first {
3143                result_suffix = suffix;
3144                first = false;
3145            } else {
3146                result_suffix = check_suffix_compatibility(result_suffix, suffix)?;
3147            }
3148
3149            sum_real += real;
3150            sum_imag += imag;
3151        }
3152
3153        let result = format_complex(sum_real, sum_imag, result_suffix);
3154        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3155    }
3156}
3157
3158/// Returns the difference between two complex numbers.
3159///
3160/// Subtracts the second complex value from the first.
3161///
3162/// # Remarks
3163/// - Inputs are coerced to complex-number text before parsing.
3164/// - Output keeps the suffix style from the parsed inputs.
3165/// - Invalid complex text returns `#NUM!`.
3166///
3167/// # Examples
3168/// ```yaml,sandbox
3169/// title: "Subtract a+bi values"
3170/// formula: "=IMSUB(\"5+3i\",\"2+i\")"
3171/// expected: "3+2i"
3172/// ```
3173///
3174/// ```yaml,sandbox
3175/// title: "Subtract pure imaginary from real"
3176/// formula: "=IMSUB(\"4\",\"7j\")"
3177/// expected: "4-7j"
3178/// ```
3179/// ```yaml,docs
3180/// related:
3181///   - IMSUM
3182///   - IMDIV
3183///   - COMPLEX
3184/// faq:
3185///   - q: "How is subtraction ordered in `IMSUB`?"
3186///     a: "It always computes `inumber1 - inumber2`; swapping arguments changes the sign of the result."
3187/// ```
3188#[derive(Debug)]
3189pub struct ImSubFn;
3190/// [formualizer-docgen:schema:start]
3191/// Name: IMSUB
3192/// Type: ImSubFn
3193/// Min args: 2
3194/// Max args: 2
3195/// Variadic: false
3196/// Signature: IMSUB(arg1: any@scalar, arg2: any@scalar)
3197/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3198/// Caps: PURE
3199/// [formualizer-docgen:schema:end]
3200impl Function for ImSubFn {
3201    func_caps!(PURE);
3202    fn name(&self) -> &'static str {
3203        "IMSUB"
3204    }
3205    fn min_args(&self) -> usize {
3206        2
3207    }
3208    fn arg_schema(&self) -> &'static [ArgSchema] {
3209        &ARG_ANY_TWO[..]
3210    }
3211    fn eval<'a, 'b, 'c>(
3212        &self,
3213        args: &'c [ArgumentHandle<'a, 'b>],
3214        _ctx: &dyn FunctionContext<'b>,
3215    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3216        let inumber1 = match args[0].value()?.into_literal() {
3217            LiteralValue::Error(e) => {
3218                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3219            }
3220            other => match coerce_complex_str(&other) {
3221                Ok(s) => s,
3222                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3223            },
3224        };
3225
3226        let inumber2 = match args[1].value()?.into_literal() {
3227            LiteralValue::Error(e) => {
3228                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3229            }
3230            other => match coerce_complex_str(&other) {
3231                Ok(s) => s,
3232                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3233            },
3234        };
3235
3236        let (real1, imag1, suffix1) = match parse_complex(&inumber1) {
3237            Ok(c) => c,
3238            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3239        };
3240
3241        let (real2, imag2, suffix2) = match parse_complex(&inumber2) {
3242            Ok(c) => c,
3243            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3244        };
3245
3246        let result_suffix = check_suffix_compatibility(suffix1, suffix2)?;
3247        let result = format_complex(real1 - real2, imag1 - imag2, result_suffix);
3248        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3249    }
3250}
3251
3252/// Returns the product of one or more complex numbers.
3253///
3254/// Multiplies values sequentially using complex multiplication rules.
3255///
3256/// # Remarks
3257/// - Each argument is coerced to complex-number text before parsing.
3258/// - Accepts any number of arguments from one upward.
3259/// - Invalid complex text returns `#NUM!`.
3260///
3261/// # Examples
3262/// ```yaml,sandbox
3263/// title: "Multiply conjugates"
3264/// formula: "=IMPRODUCT(\"1+i\",\"1-i\")"
3265/// expected: "2"
3266/// ```
3267///
3268/// ```yaml,sandbox
3269/// title: "Scale an imaginary value"
3270/// formula: "=IMPRODUCT(\"2i\",\"3\")"
3271/// expected: "6i"
3272/// ```
3273/// ```yaml,docs
3274/// related:
3275///   - IMDIV
3276///   - IMSUM
3277///   - IMPOWER
3278/// faq:
3279///   - q: "Can `IMPRODUCT` multiply a single argument?"
3280///     a: "Yes. With one argument it returns that parsed complex value in canonical formatted form."
3281/// ```
3282#[derive(Debug)]
3283pub struct ImProductFn;
3284/// [formualizer-docgen:schema:start]
3285/// Name: IMPRODUCT
3286/// Type: ImProductFn
3287/// Min args: 1
3288/// Max args: variadic
3289/// Variadic: true
3290/// Signature: IMPRODUCT(arg1...: any@scalar)
3291/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3292/// Caps: PURE
3293/// [formualizer-docgen:schema:end]
3294impl Function for ImProductFn {
3295    func_caps!(PURE);
3296    fn name(&self) -> &'static str {
3297        "IMPRODUCT"
3298    }
3299    fn min_args(&self) -> usize {
3300        1
3301    }
3302    fn variadic(&self) -> bool {
3303        true
3304    }
3305    fn arg_schema(&self) -> &'static [ArgSchema] {
3306        &ARG_ANY_ONE[..]
3307    }
3308    fn eval<'a, 'b, 'c>(
3309        &self,
3310        args: &'c [ArgumentHandle<'a, 'b>],
3311        _ctx: &dyn FunctionContext<'b>,
3312    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3313        let mut prod_real = 1.0;
3314        let mut prod_imag = 0.0;
3315        let mut result_suffix = 'i';
3316        let mut first = true;
3317
3318        for arg in args {
3319            let inumber = match arg.value()?.into_literal() {
3320                LiteralValue::Error(e) => {
3321                    return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3322                }
3323                other => match coerce_complex_str(&other) {
3324                    Ok(s) => s,
3325                    Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3326                },
3327            };
3328
3329            let (real, imag, suffix) = match parse_complex(&inumber) {
3330                Ok(c) => c,
3331                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3332            };
3333
3334            if first {
3335                result_suffix = suffix;
3336                prod_real = real;
3337                prod_imag = imag;
3338                first = false;
3339            } else {
3340                result_suffix = check_suffix_compatibility(result_suffix, suffix)?;
3341                // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
3342                let new_real = prod_real * real - prod_imag * imag;
3343                let new_imag = prod_real * imag + prod_imag * real;
3344                prod_real = new_real;
3345                prod_imag = new_imag;
3346            }
3347        }
3348
3349        let result = format_complex(prod_real, prod_imag, result_suffix);
3350        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3351    }
3352}
3353
3354/// Returns the quotient of two complex numbers.
3355///
3356/// Divides the first complex value by the second.
3357///
3358/// # Remarks
3359/// - Inputs are coerced to complex-number text before parsing.
3360/// - Returns `#DIV/0!` when the divisor is `0+0i`.
3361/// - Invalid complex text returns `#NUM!`.
3362///
3363/// # Examples
3364/// ```yaml,sandbox
3365/// title: "Divide complex numbers"
3366/// formula: "=IMDIV(\"3+4i\",\"1-i\")"
3367/// expected: "-0.5+3.5i"
3368/// ```
3369///
3370/// ```yaml,sandbox
3371/// title: "Division by zero complex"
3372/// formula: "=IMDIV(\"2+i\",\"0\")"
3373/// expected: "#DIV/0!"
3374/// ```
3375/// ```yaml,docs
3376/// related:
3377///   - IMPRODUCT
3378///   - IMSUB
3379///   - IMCONJUGATE
3380/// faq:
3381///   - q: "When does `IMDIV` return `#DIV/0!`?"
3382///     a: "If the divisor is `0+0i` (denominator magnitude near zero), division is undefined and returns `#DIV/0!`."
3383/// ```
3384#[derive(Debug)]
3385pub struct ImDivFn;
3386/// [formualizer-docgen:schema:start]
3387/// Name: IMDIV
3388/// Type: ImDivFn
3389/// Min args: 2
3390/// Max args: 2
3391/// Variadic: false
3392/// Signature: IMDIV(arg1: any@scalar, arg2: any@scalar)
3393/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3394/// Caps: PURE
3395/// [formualizer-docgen:schema:end]
3396impl Function for ImDivFn {
3397    func_caps!(PURE);
3398    fn name(&self) -> &'static str {
3399        "IMDIV"
3400    }
3401    fn min_args(&self) -> usize {
3402        2
3403    }
3404    fn arg_schema(&self) -> &'static [ArgSchema] {
3405        &ARG_ANY_TWO[..]
3406    }
3407    fn eval<'a, 'b, 'c>(
3408        &self,
3409        args: &'c [ArgumentHandle<'a, 'b>],
3410        _ctx: &dyn FunctionContext<'b>,
3411    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3412        let inumber1 = match args[0].value()?.into_literal() {
3413            LiteralValue::Error(e) => {
3414                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3415            }
3416            other => match coerce_complex_str(&other) {
3417                Ok(s) => s,
3418                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3419            },
3420        };
3421
3422        let inumber2 = match args[1].value()?.into_literal() {
3423            LiteralValue::Error(e) => {
3424                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3425            }
3426            other => match coerce_complex_str(&other) {
3427                Ok(s) => s,
3428                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3429            },
3430        };
3431
3432        let (a, b, suffix1) = match parse_complex(&inumber1) {
3433            Ok(c) => c,
3434            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3435        };
3436
3437        let (c, d, suffix2) = match parse_complex(&inumber2) {
3438            Ok(c) => c,
3439            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3440        };
3441
3442        // Division by zero check - returns #DIV/0! for Excel compatibility
3443        let denom = c * c + d * d;
3444        if denom.abs() < 1e-15 {
3445            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3446                ExcelError::new_div(),
3447            )));
3448        }
3449
3450        let result_suffix = check_suffix_compatibility(suffix1, suffix2)?;
3451
3452        // (a + bi) / (c + di) = ((ac + bd) + (bc - ad)i) / (c^2 + d^2)
3453        let real = (a * c + b * d) / denom;
3454        let imag = (b * c - a * d) / denom;
3455
3456        let result = format_complex(real, imag, result_suffix);
3457        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3458    }
3459}
3460
3461/// Returns the complex exponential of a complex number.
3462///
3463/// Computes `e^(a+bi)` and returns the result as complex text.
3464///
3465/// # Remarks
3466/// - Input is coerced to complex-number text before parsing.
3467/// - Uses Euler's identity for the imaginary component.
3468/// - Invalid complex text returns `#NUM!`.
3469///
3470/// # Examples
3471/// ```yaml,sandbox
3472/// title: "Exponential of zero"
3473/// formula: "=IMEXP(\"0\")"
3474/// expected: "1"
3475/// ```
3476///
3477/// ```yaml,sandbox
3478/// title: "Exponential of a real value"
3479/// formula: "=IMEXP(\"1\")"
3480/// expected: "2.718281828459045"
3481/// ```
3482/// ```yaml,docs
3483/// related:
3484///   - IMLN
3485///   - IMPOWER
3486///   - IMSIN
3487///   - IMCOS
3488/// faq:
3489///   - q: "Does `IMEXP` return text or a numeric complex type?"
3490///     a: "It returns a canonical complex text string, consistent with other `IM*` functions."
3491/// ```
3492#[derive(Debug)]
3493pub struct ImExpFn;
3494/// [formualizer-docgen:schema:start]
3495/// Name: IMEXP
3496/// Type: ImExpFn
3497/// Min args: 1
3498/// Max args: 1
3499/// Variadic: false
3500/// Signature: IMEXP(arg1: any@scalar)
3501/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3502/// Caps: PURE
3503/// [formualizer-docgen:schema:end]
3504impl Function for ImExpFn {
3505    func_caps!(PURE);
3506    fn name(&self) -> &'static str {
3507        "IMEXP"
3508    }
3509    fn min_args(&self) -> usize {
3510        1
3511    }
3512    fn arg_schema(&self) -> &'static [ArgSchema] {
3513        &ARG_ANY_ONE[..]
3514    }
3515    fn eval<'a, 'b, 'c>(
3516        &self,
3517        args: &'c [ArgumentHandle<'a, 'b>],
3518        _ctx: &dyn FunctionContext<'b>,
3519    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3520        let inumber = match args[0].value()?.into_literal() {
3521            LiteralValue::Error(e) => {
3522                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3523            }
3524            other => match coerce_complex_str(&other) {
3525                Ok(s) => s,
3526                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3527            },
3528        };
3529
3530        let (a, b, suffix) = match parse_complex(&inumber) {
3531            Ok(c) => c,
3532            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3533        };
3534
3535        // e^(a+bi) = e^a * (cos(b) + i*sin(b))
3536        let exp_a = a.exp();
3537        let real = exp_a * b.cos();
3538        let imag = exp_a * b.sin();
3539
3540        let result = format_complex(real, imag, suffix);
3541        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3542    }
3543}
3544
3545/// Returns the natural logarithm of a complex number.
3546///
3547/// Produces the principal complex logarithm as text.
3548///
3549/// # Remarks
3550/// - Input is coerced to complex-number text before parsing.
3551/// - Returns `#NUM!` for zero input because `ln(0)` is undefined.
3552/// - Invalid complex text returns `#NUM!`.
3553///
3554/// # Examples
3555/// ```yaml,sandbox
3556/// title: "Natural log of 1"
3557/// formula: "=IMLN(\"1\")"
3558/// expected: "0"
3559/// ```
3560///
3561/// ```yaml,sandbox
3562/// title: "Natural log on imaginary axis"
3563/// formula: "=IMLN(\"i\")"
3564/// expected: "1.5707963267948966i"
3565/// ```
3566/// ```yaml,docs
3567/// related:
3568///   - IMEXP
3569///   - IMLOG10
3570///   - IMLOG2
3571/// faq:
3572///   - q: "Why does `IMLN(0)` return `#NUM!`?"
3573///     a: "The complex logarithm at zero is undefined, so this implementation returns `#NUM!`."
3574/// ```
3575#[derive(Debug)]
3576pub struct ImLnFn;
3577/// [formualizer-docgen:schema:start]
3578/// Name: IMLN
3579/// Type: ImLnFn
3580/// Min args: 1
3581/// Max args: 1
3582/// Variadic: false
3583/// Signature: IMLN(arg1: any@scalar)
3584/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3585/// Caps: PURE
3586/// [formualizer-docgen:schema:end]
3587impl Function for ImLnFn {
3588    func_caps!(PURE);
3589    fn name(&self) -> &'static str {
3590        "IMLN"
3591    }
3592    fn min_args(&self) -> usize {
3593        1
3594    }
3595    fn arg_schema(&self) -> &'static [ArgSchema] {
3596        &ARG_ANY_ONE[..]
3597    }
3598    fn eval<'a, 'b, 'c>(
3599        &self,
3600        args: &'c [ArgumentHandle<'a, 'b>],
3601        _ctx: &dyn FunctionContext<'b>,
3602    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3603        let inumber = match args[0].value()?.into_literal() {
3604            LiteralValue::Error(e) => {
3605                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3606            }
3607            other => match coerce_complex_str(&other) {
3608                Ok(s) => s,
3609                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3610            },
3611        };
3612
3613        let (a, b, suffix) = match parse_complex(&inumber) {
3614            Ok(c) => c,
3615            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3616        };
3617
3618        // ln(0) is undefined
3619        let modulus = (a * a + b * b).sqrt();
3620        if modulus < 1e-15 {
3621            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3622                ExcelError::new_num(),
3623            )));
3624        }
3625
3626        // ln(z) = ln(|z|) + i*arg(z)
3627        let real = modulus.ln();
3628        let imag = b.atan2(a);
3629
3630        let result = format_complex(real, imag, suffix);
3631        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3632    }
3633}
3634
3635/// Returns the base-10 logarithm of a complex number.
3636///
3637/// Produces the principal complex logarithm in base 10.
3638///
3639/// # Remarks
3640/// - Input is coerced to complex-number text before parsing.
3641/// - Returns `#NUM!` for zero input.
3642/// - Invalid complex text returns `#NUM!`.
3643///
3644/// # Examples
3645/// ```yaml,sandbox
3646/// title: "Base-10 log of a real value"
3647/// formula: "=IMLOG10(\"10\")"
3648/// expected: "1"
3649/// ```
3650///
3651/// ```yaml,sandbox
3652/// title: "Base-10 log on imaginary axis"
3653/// formula: "=IMLOG10(\"i\")"
3654/// expected: "0.6821881769209206i"
3655/// ```
3656/// ```yaml,docs
3657/// related:
3658///   - IMLN
3659///   - IMLOG2
3660///   - IMEXP
3661/// faq:
3662///   - q: "What branch of the logarithm does `IMLOG10` use?"
3663///     a: "It returns the principal complex logarithm (base 10), derived from principal argument `atan2(imag, real)`."
3664/// ```
3665#[derive(Debug)]
3666pub struct ImLog10Fn;
3667/// [formualizer-docgen:schema:start]
3668/// Name: IMLOG10
3669/// Type: ImLog10Fn
3670/// Min args: 1
3671/// Max args: 1
3672/// Variadic: false
3673/// Signature: IMLOG10(arg1: any@scalar)
3674/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3675/// Caps: PURE
3676/// [formualizer-docgen:schema:end]
3677impl Function for ImLog10Fn {
3678    func_caps!(PURE);
3679    fn name(&self) -> &'static str {
3680        "IMLOG10"
3681    }
3682    fn min_args(&self) -> usize {
3683        1
3684    }
3685    fn arg_schema(&self) -> &'static [ArgSchema] {
3686        &ARG_ANY_ONE[..]
3687    }
3688    fn eval<'a, 'b, 'c>(
3689        &self,
3690        args: &'c [ArgumentHandle<'a, 'b>],
3691        _ctx: &dyn FunctionContext<'b>,
3692    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3693        let inumber = match args[0].value()?.into_literal() {
3694            LiteralValue::Error(e) => {
3695                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3696            }
3697            other => match coerce_complex_str(&other) {
3698                Ok(s) => s,
3699                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3700            },
3701        };
3702
3703        let (a, b, suffix) = match parse_complex(&inumber) {
3704            Ok(c) => c,
3705            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3706        };
3707
3708        // log10(0) is undefined
3709        let modulus = (a * a + b * b).sqrt();
3710        if modulus < 1e-15 {
3711            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3712                ExcelError::new_num(),
3713            )));
3714        }
3715
3716        // log10(z) = ln(z) / ln(10) = (ln(|z|) + i*arg(z)) / ln(10)
3717        let ln10 = 10.0_f64.ln();
3718        let real = modulus.ln() / ln10;
3719        let imag = b.atan2(a) / ln10;
3720
3721        let result = format_complex(real, imag, suffix);
3722        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3723    }
3724}
3725
3726/// Returns the base-2 logarithm of a complex number.
3727///
3728/// Produces the principal complex logarithm in base 2.
3729///
3730/// # Remarks
3731/// - Input is coerced to complex-number text before parsing.
3732/// - Returns `#NUM!` for zero input.
3733/// - Invalid complex text returns `#NUM!`.
3734///
3735/// # Examples
3736/// ```yaml,sandbox
3737/// title: "Base-2 log of a real value"
3738/// formula: "=IMLOG2(\"8\")"
3739/// expected: "3"
3740/// ```
3741///
3742/// ```yaml,sandbox
3743/// title: "Base-2 log on imaginary axis"
3744/// formula: "=IMLOG2(\"i\")"
3745/// expected: "2.266180070913597i"
3746/// ```
3747/// ```yaml,docs
3748/// related:
3749///   - IMLN
3750///   - IMLOG10
3751///   - IMEXP
3752/// faq:
3753///   - q: "When does `IMLOG2` return `#NUM!`?"
3754///     a: "It returns `#NUM!` for invalid complex text or zero input, where logarithm is undefined."
3755/// ```
3756#[derive(Debug)]
3757pub struct ImLog2Fn;
3758/// [formualizer-docgen:schema:start]
3759/// Name: IMLOG2
3760/// Type: ImLog2Fn
3761/// Min args: 1
3762/// Max args: 1
3763/// Variadic: false
3764/// Signature: IMLOG2(arg1: any@scalar)
3765/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3766/// Caps: PURE
3767/// [formualizer-docgen:schema:end]
3768impl Function for ImLog2Fn {
3769    func_caps!(PURE);
3770    fn name(&self) -> &'static str {
3771        "IMLOG2"
3772    }
3773    fn min_args(&self) -> usize {
3774        1
3775    }
3776    fn arg_schema(&self) -> &'static [ArgSchema] {
3777        &ARG_ANY_ONE[..]
3778    }
3779    fn eval<'a, 'b, 'c>(
3780        &self,
3781        args: &'c [ArgumentHandle<'a, 'b>],
3782        _ctx: &dyn FunctionContext<'b>,
3783    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3784        let inumber = match args[0].value()?.into_literal() {
3785            LiteralValue::Error(e) => {
3786                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3787            }
3788            other => match coerce_complex_str(&other) {
3789                Ok(s) => s,
3790                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3791            },
3792        };
3793
3794        let (a, b, suffix) = match parse_complex(&inumber) {
3795            Ok(c) => c,
3796            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3797        };
3798
3799        // log2(0) is undefined
3800        let modulus = (a * a + b * b).sqrt();
3801        if modulus < 1e-15 {
3802            return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3803                ExcelError::new_num(),
3804            )));
3805        }
3806
3807        // log2(z) = ln(z) / ln(2) = (ln(|z|) + i*arg(z)) / ln(2)
3808        let ln2 = 2.0_f64.ln();
3809        let real = modulus.ln() / ln2;
3810        let imag = b.atan2(a) / ln2;
3811
3812        let result = format_complex(real, imag, suffix);
3813        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3814    }
3815}
3816
3817/// Raises a complex number to a real power.
3818///
3819/// Uses polar form and returns the principal-value result as complex text.
3820///
3821/// # Remarks
3822/// - `inumber` is coerced to complex-number text; `n` is numerically coerced.
3823/// - Returns `#NUM!` for undefined zero-power cases such as `0^0` or `0^-1`.
3824/// - Invalid complex text returns `#NUM!`.
3825///
3826/// # Examples
3827/// ```yaml,sandbox
3828/// title: "Square a complex value"
3829/// formula: "=IMPOWER(\"1+i\",2)"
3830/// expected: "2i"
3831/// ```
3832///
3833/// ```yaml,sandbox
3834/// title: "Negative real exponent"
3835/// formula: "=IMPOWER(\"2\",-1)"
3836/// expected: "0.5"
3837/// ```
3838/// ```yaml,docs
3839/// related:
3840///   - IMSQRT
3841///   - IMEXP
3842///   - IMLN
3843/// faq:
3844///   - q: "How does `IMPOWER` handle zero base with non-positive exponent?"
3845///     a: "`0^0` and `0` raised to a negative exponent are treated as undefined and return `#NUM!`."
3846/// ```
3847#[derive(Debug)]
3848pub struct ImPowerFn;
3849/// [formualizer-docgen:schema:start]
3850/// Name: IMPOWER
3851/// Type: ImPowerFn
3852/// Min args: 2
3853/// Max args: 2
3854/// Variadic: false
3855/// Signature: IMPOWER(arg1: any@scalar, arg2: any@scalar)
3856/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3857/// Caps: PURE
3858/// [formualizer-docgen:schema:end]
3859impl Function for ImPowerFn {
3860    func_caps!(PURE);
3861    fn name(&self) -> &'static str {
3862        "IMPOWER"
3863    }
3864    fn min_args(&self) -> usize {
3865        2
3866    }
3867    fn arg_schema(&self) -> &'static [ArgSchema] {
3868        &ARG_ANY_TWO[..]
3869    }
3870    fn eval<'a, 'b, 'c>(
3871        &self,
3872        args: &'c [ArgumentHandle<'a, 'b>],
3873        _ctx: &dyn FunctionContext<'b>,
3874    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3875        let inumber = match args[0].value()?.into_literal() {
3876            LiteralValue::Error(e) => {
3877                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3878            }
3879            other => match coerce_complex_str(&other) {
3880                Ok(s) => s,
3881                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3882            },
3883        };
3884
3885        let n = match args[1].value()?.into_literal() {
3886            LiteralValue::Error(e) => {
3887                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3888            }
3889            other => coerce_num(&other)?,
3890        };
3891
3892        let (a, b, suffix) = match parse_complex(&inumber) {
3893            Ok(c) => c,
3894            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3895        };
3896
3897        let modulus = (a * a + b * b).sqrt();
3898        let theta = b.atan2(a);
3899
3900        // Handle 0^n cases
3901        if modulus < 1e-15 {
3902            if n > 0.0 {
3903                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
3904                    "0".to_string(),
3905                )));
3906            } else {
3907                // 0^0 or 0^negative is undefined
3908                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
3909                    ExcelError::new_num(),
3910                )));
3911            }
3912        }
3913
3914        // z^n = |z|^n * (cos(n*theta) + i*sin(n*theta))
3915        let r_n = modulus.powf(n);
3916        let n_theta = n * theta;
3917        let real = r_n * n_theta.cos();
3918        let imag = r_n * n_theta.sin();
3919
3920        let result = format_complex(real, imag, suffix);
3921        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
3922    }
3923}
3924
3925/// Returns the principal square root of a complex number.
3926///
3927/// Computes the root in polar form and returns complex text.
3928///
3929/// # Remarks
3930/// - Input is coerced to complex-number text before parsing.
3931/// - Returns the principal branch of the square root.
3932/// - Invalid complex text returns `#NUM!`.
3933///
3934/// # Examples
3935/// ```yaml,sandbox
3936/// title: "Square root of a negative real"
3937/// formula: "=IMSQRT(\"-4\")"
3938/// expected: "2i"
3939/// ```
3940///
3941/// ```yaml,sandbox
3942/// title: "Square root of a+bi"
3943/// formula: "=IMSQRT(\"3+4i\")"
3944/// expected: "2+i"
3945/// ```
3946/// ```yaml,docs
3947/// related:
3948///   - IMPOWER
3949///   - IMABS
3950///   - IMARGUMENT
3951/// faq:
3952///   - q: "Which square root does `IMSQRT` return for complex inputs?"
3953///     a: "It returns the principal branch (half-angle polar form), matching spreadsheet-style principal-value behavior."
3954/// ```
3955#[derive(Debug)]
3956pub struct ImSqrtFn;
3957/// [formualizer-docgen:schema:start]
3958/// Name: IMSQRT
3959/// Type: ImSqrtFn
3960/// Min args: 1
3961/// Max args: 1
3962/// Variadic: false
3963/// Signature: IMSQRT(arg1: any@scalar)
3964/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
3965/// Caps: PURE
3966/// [formualizer-docgen:schema:end]
3967impl Function for ImSqrtFn {
3968    func_caps!(PURE);
3969    fn name(&self) -> &'static str {
3970        "IMSQRT"
3971    }
3972    fn min_args(&self) -> usize {
3973        1
3974    }
3975    fn arg_schema(&self) -> &'static [ArgSchema] {
3976        &ARG_ANY_ONE[..]
3977    }
3978    fn eval<'a, 'b, 'c>(
3979        &self,
3980        args: &'c [ArgumentHandle<'a, 'b>],
3981        _ctx: &dyn FunctionContext<'b>,
3982    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3983        let inumber = match args[0].value()?.into_literal() {
3984            LiteralValue::Error(e) => {
3985                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
3986            }
3987            other => match coerce_complex_str(&other) {
3988                Ok(s) => s,
3989                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3990            },
3991        };
3992
3993        let (a, b, suffix) = match parse_complex(&inumber) {
3994            Ok(c) => c,
3995            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
3996        };
3997
3998        let modulus = (a * a + b * b).sqrt();
3999        let theta = b.atan2(a);
4000
4001        // sqrt(z) = sqrt(|z|) * (cos(theta/2) + i*sin(theta/2))
4002        let sqrt_r = modulus.sqrt();
4003        let half_theta = theta / 2.0;
4004        let real = sqrt_r * half_theta.cos();
4005        let imag = sqrt_r * half_theta.sin();
4006
4007        let result = format_complex(real, imag, suffix);
4008        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
4009    }
4010}
4011
4012/// Returns the sine of a complex number.
4013///
4014/// Evaluates complex sine and returns the result as complex text.
4015///
4016/// # Remarks
4017/// - Input is coerced to complex-number text before parsing.
4018/// - Uses hyperbolic components for the imaginary part.
4019/// - Invalid complex text returns `#NUM!`.
4020///
4021/// # Examples
4022/// ```yaml,sandbox
4023/// title: "Sine of zero"
4024/// formula: "=IMSIN(\"0\")"
4025/// expected: "0"
4026/// ```
4027///
4028/// ```yaml,sandbox
4029/// title: "Sine on imaginary axis"
4030/// formula: "=IMSIN(\"i\")"
4031/// expected: "1.1752011936438014i"
4032/// ```
4033/// ```yaml,docs
4034/// related:
4035///   - IMCOS
4036///   - IMEXP
4037/// faq:
4038///   - q: "Why can `IMSIN` return non-zero imaginary output for real-looking formulas?"
4039///     a: "For complex inputs `a+bi`, sine uses hyperbolic terms (`cosh`, `sinh`), so imaginary components are expected."
4040/// ```
4041#[derive(Debug)]
4042pub struct ImSinFn;
4043/// [formualizer-docgen:schema:start]
4044/// Name: IMSIN
4045/// Type: ImSinFn
4046/// Min args: 1
4047/// Max args: 1
4048/// Variadic: false
4049/// Signature: IMSIN(arg1: any@scalar)
4050/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
4051/// Caps: PURE
4052/// [formualizer-docgen:schema:end]
4053impl Function for ImSinFn {
4054    func_caps!(PURE);
4055    fn name(&self) -> &'static str {
4056        "IMSIN"
4057    }
4058    fn min_args(&self) -> usize {
4059        1
4060    }
4061    fn arg_schema(&self) -> &'static [ArgSchema] {
4062        &ARG_ANY_ONE[..]
4063    }
4064    fn eval<'a, 'b, 'c>(
4065        &self,
4066        args: &'c [ArgumentHandle<'a, 'b>],
4067        _ctx: &dyn FunctionContext<'b>,
4068    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
4069        let inumber = match args[0].value()?.into_literal() {
4070            LiteralValue::Error(e) => {
4071                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
4072            }
4073            other => match coerce_complex_str(&other) {
4074                Ok(s) => s,
4075                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
4076            },
4077        };
4078
4079        let (a, b, suffix) = match parse_complex(&inumber) {
4080            Ok(c) => c,
4081            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
4082        };
4083
4084        // sin(a+bi) = sin(a)*cosh(b) + i*cos(a)*sinh(b)
4085        let real = a.sin() * b.cosh();
4086        let imag = a.cos() * b.sinh();
4087
4088        let result = format_complex(real, imag, suffix);
4089        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
4090    }
4091}
4092
4093/// Returns the cosine of a complex number.
4094///
4095/// Evaluates complex cosine and returns the result as complex text.
4096///
4097/// # Remarks
4098/// - Input is coerced to complex-number text before parsing.
4099/// - Uses hyperbolic components for the imaginary part.
4100/// - Invalid complex text returns `#NUM!`.
4101///
4102/// # Examples
4103/// ```yaml,sandbox
4104/// title: "Cosine of zero"
4105/// formula: "=IMCOS(\"0\")"
4106/// expected: "1"
4107/// ```
4108///
4109/// ```yaml,sandbox
4110/// title: "Cosine on imaginary axis"
4111/// formula: "=IMCOS(\"i\")"
4112/// expected: "1.5430806348152437"
4113/// ```
4114/// ```yaml,docs
4115/// related:
4116///   - IMSIN
4117///   - IMEXP
4118/// faq:
4119///   - q: "Why is the imaginary part negated in `IMCOS`?"
4120///     a: "Complex cosine uses `cos(a+bi)=cos(a)cosh(b)-i sin(a)sinh(b)`, so the imaginary term carries a minus sign."
4121/// ```
4122#[derive(Debug)]
4123pub struct ImCosFn;
4124/// [formualizer-docgen:schema:start]
4125/// Name: IMCOS
4126/// Type: ImCosFn
4127/// Min args: 1
4128/// Max args: 1
4129/// Variadic: false
4130/// Signature: IMCOS(arg1: any@scalar)
4131/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
4132/// Caps: PURE
4133/// [formualizer-docgen:schema:end]
4134impl Function for ImCosFn {
4135    func_caps!(PURE);
4136    fn name(&self) -> &'static str {
4137        "IMCOS"
4138    }
4139    fn min_args(&self) -> usize {
4140        1
4141    }
4142    fn arg_schema(&self) -> &'static [ArgSchema] {
4143        &ARG_ANY_ONE[..]
4144    }
4145    fn eval<'a, 'b, 'c>(
4146        &self,
4147        args: &'c [ArgumentHandle<'a, 'b>],
4148        _ctx: &dyn FunctionContext<'b>,
4149    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
4150        let inumber = match args[0].value()?.into_literal() {
4151            LiteralValue::Error(e) => {
4152                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
4153            }
4154            other => match coerce_complex_str(&other) {
4155                Ok(s) => s,
4156                Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
4157            },
4158        };
4159
4160        let (a, b, suffix) = match parse_complex(&inumber) {
4161            Ok(c) => c,
4162            Err(e) => return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
4163        };
4164
4165        // cos(a+bi) = cos(a)*cosh(b) - i*sin(a)*sinh(b)
4166        let real = a.cos() * b.cosh();
4167        let imag = -a.sin() * b.sinh();
4168
4169        let result = format_complex(real, imag, suffix);
4170        Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(result)))
4171    }
4172}
4173
4174/* ─────────────────────────── Unit Conversion (CONVERT) ──────────────────────────── */
4175
4176/// Unit categories for CONVERT function
4177#[derive(Clone, Copy, PartialEq, Eq, Debug)]
4178enum UnitCategory {
4179    Length,
4180    Mass,
4181    Temperature,
4182}
4183
4184/// Information about a unit
4185struct UnitInfo {
4186    category: UnitCategory,
4187    /// Conversion factor to base unit (meters for length, grams for mass)
4188    /// For temperature, this is special-cased
4189    to_base: f64,
4190}
4191
4192/// Get unit info for a given unit string
4193fn get_unit_info(unit: &str) -> Option<UnitInfo> {
4194    // Length units (base: meter)
4195    match unit {
4196        // Metric length
4197        "m" => Some(UnitInfo {
4198            category: UnitCategory::Length,
4199            to_base: 1.0,
4200        }),
4201        "km" => Some(UnitInfo {
4202            category: UnitCategory::Length,
4203            to_base: 1000.0,
4204        }),
4205        "cm" => Some(UnitInfo {
4206            category: UnitCategory::Length,
4207            to_base: 0.01,
4208        }),
4209        "mm" => Some(UnitInfo {
4210            category: UnitCategory::Length,
4211            to_base: 0.001,
4212        }),
4213        // Imperial length
4214        "mi" => Some(UnitInfo {
4215            category: UnitCategory::Length,
4216            to_base: 1609.344,
4217        }),
4218        "ft" => Some(UnitInfo {
4219            category: UnitCategory::Length,
4220            to_base: 0.3048,
4221        }),
4222        "in" => Some(UnitInfo {
4223            category: UnitCategory::Length,
4224            to_base: 0.0254,
4225        }),
4226        "yd" => Some(UnitInfo {
4227            category: UnitCategory::Length,
4228            to_base: 0.9144,
4229        }),
4230        "Nmi" => Some(UnitInfo {
4231            category: UnitCategory::Length,
4232            to_base: 1852.0,
4233        }),
4234
4235        // Mass units (base: gram)
4236        "g" => Some(UnitInfo {
4237            category: UnitCategory::Mass,
4238            to_base: 1.0,
4239        }),
4240        "kg" => Some(UnitInfo {
4241            category: UnitCategory::Mass,
4242            to_base: 1000.0,
4243        }),
4244        "mg" => Some(UnitInfo {
4245            category: UnitCategory::Mass,
4246            to_base: 0.001,
4247        }),
4248        "lbm" => Some(UnitInfo {
4249            category: UnitCategory::Mass,
4250            to_base: 453.59237,
4251        }),
4252        "oz" => Some(UnitInfo {
4253            category: UnitCategory::Mass,
4254            to_base: 28.349523125,
4255        }),
4256        "ozm" => Some(UnitInfo {
4257            category: UnitCategory::Mass,
4258            to_base: 28.349523125,
4259        }),
4260        "ton" => Some(UnitInfo {
4261            category: UnitCategory::Mass,
4262            to_base: 907184.74,
4263        }),
4264
4265        // Temperature units (special handling)
4266        "C" | "cel" => Some(UnitInfo {
4267            category: UnitCategory::Temperature,
4268            to_base: 0.0, // Special-cased
4269        }),
4270        "F" | "fah" => Some(UnitInfo {
4271            category: UnitCategory::Temperature,
4272            to_base: 0.0, // Special-cased
4273        }),
4274        "K" | "kel" => Some(UnitInfo {
4275            category: UnitCategory::Temperature,
4276            to_base: 0.0, // Special-cased
4277        }),
4278
4279        _ => None,
4280    }
4281}
4282
4283/// Normalize temperature unit name
4284fn normalize_temp_unit(unit: &str) -> &str {
4285    match unit {
4286        "C" | "cel" => "C",
4287        "F" | "fah" => "F",
4288        "K" | "kel" => "K",
4289        _ => unit,
4290    }
4291}
4292
4293/// Convert temperature between units
4294fn convert_temperature(value: f64, from: &str, to: &str) -> f64 {
4295    let from = normalize_temp_unit(from);
4296    let to = normalize_temp_unit(to);
4297
4298    if from == to {
4299        return value;
4300    }
4301
4302    // First convert to Celsius
4303    let celsius = match from {
4304        "C" => value,
4305        "F" => (value - 32.0) * 5.0 / 9.0,
4306        "K" => value - 273.15,
4307        _ => value,
4308    };
4309
4310    // Then convert from Celsius to target
4311    match to {
4312        "C" => celsius,
4313        "F" => celsius * 9.0 / 5.0 + 32.0,
4314        "K" => celsius + 273.15,
4315        _ => celsius,
4316    }
4317}
4318
4319/// Convert a value between units
4320fn convert_units(value: f64, from: &str, to: &str) -> Result<f64, ExcelError> {
4321    let from_info = get_unit_info(from).ok_or_else(ExcelError::new_na)?;
4322    let to_info = get_unit_info(to).ok_or_else(ExcelError::new_na)?;
4323
4324    // Check category compatibility
4325    if from_info.category != to_info.category {
4326        return Err(ExcelError::new_na());
4327    }
4328
4329    // Handle temperature specially
4330    if from_info.category == UnitCategory::Temperature {
4331        return Ok(convert_temperature(value, from, to));
4332    }
4333
4334    // For other units: convert to base, then to target
4335    let base_value = value * from_info.to_base;
4336    Ok(base_value / to_info.to_base)
4337}
4338
4339/// Converts a numeric value from one supported unit to another.
4340///
4341/// Supports a focused set of length, mass, and temperature units.
4342///
4343/// # Remarks
4344/// - `number` is numerically coerced; unit arguments must be text.
4345/// - Returns `#N/A` for unknown units or incompatible unit categories.
4346/// - Temperature conversions support `C/cel`, `F/fah`, and `K/kel`.
4347///
4348/// # Examples
4349/// ```yaml,sandbox
4350/// title: "Length conversion"
4351/// formula: "=CONVERT(1,\"km\",\"m\")"
4352/// expected: 1000
4353/// ```
4354///
4355/// ```yaml,sandbox
4356/// title: "Temperature conversion"
4357/// formula: "=CONVERT(32,\"F\",\"C\")"
4358/// expected: 0
4359/// ```
4360/// ```yaml,docs
4361/// related:
4362///   - DEC2BIN
4363///   - DEC2HEX
4364///   - DEC2OCT
4365/// faq:
4366///   - q: "When does `CONVERT` return `#N/A`?"
4367///     a: "Unknown unit tokens, non-text unit arguments, or mixing incompatible categories (for example length to mass) return `#N/A`."
4368/// ```
4369#[derive(Debug)]
4370pub struct ConvertFn;
4371/// [formualizer-docgen:schema:start]
4372/// Name: CONVERT
4373/// Type: ConvertFn
4374/// Min args: 3
4375/// Max args: 3
4376/// Variadic: false
4377/// Signature: CONVERT(arg1: any@scalar, arg2: any@scalar, arg3: any@scalar)
4378/// Arg schema: arg1{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg2{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}; arg3{kinds=any,required=true,shape=scalar,by_ref=false,coercion=None,max=None,repeating=None,default=false}
4379/// Caps: PURE
4380/// [formualizer-docgen:schema:end]
4381impl Function for ConvertFn {
4382    func_caps!(PURE);
4383    fn name(&self) -> &'static str {
4384        "CONVERT"
4385    }
4386    fn min_args(&self) -> usize {
4387        3
4388    }
4389    fn arg_schema(&self) -> &'static [ArgSchema] {
4390        &ARG_COMPLEX_THREE[..]
4391    }
4392    fn eval<'a, 'b, 'c>(
4393        &self,
4394        args: &'c [ArgumentHandle<'a, 'b>],
4395        _ctx: &dyn FunctionContext<'b>,
4396    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
4397        // Get the number value
4398        let value = match args[0].value()?.into_literal() {
4399            LiteralValue::Error(e) => {
4400                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
4401            }
4402            other => coerce_num(&other)?,
4403        };
4404
4405        // Get from_unit
4406        let from_unit = match args[1].value()?.into_literal() {
4407            LiteralValue::Error(e) => {
4408                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
4409            }
4410            LiteralValue::Text(s) => s,
4411            _ => {
4412                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
4413                    ExcelError::new_na(),
4414                )));
4415            }
4416        };
4417
4418        // Get to_unit
4419        let to_unit = match args[2].value()?.into_literal() {
4420            LiteralValue::Error(e) => {
4421                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
4422            }
4423            LiteralValue::Text(s) => s,
4424            _ => {
4425                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
4426                    ExcelError::new_na(),
4427                )));
4428            }
4429        };
4430
4431        match convert_units(value, &from_unit, &to_unit) {
4432            Ok(result) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
4433                result,
4434            ))),
4435            Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
4436        }
4437    }
4438}
4439
4440pub fn register_builtins() {
4441    use std::sync::Arc;
4442    crate::function_registry::register_function(Arc::new(BitAndFn));
4443    crate::function_registry::register_function(Arc::new(BitOrFn));
4444    crate::function_registry::register_function(Arc::new(BitXorFn));
4445    crate::function_registry::register_function(Arc::new(BitLShiftFn));
4446    crate::function_registry::register_function(Arc::new(BitRShiftFn));
4447    crate::function_registry::register_function(Arc::new(Bin2DecFn));
4448    crate::function_registry::register_function(Arc::new(Dec2BinFn));
4449    crate::function_registry::register_function(Arc::new(Hex2DecFn));
4450    crate::function_registry::register_function(Arc::new(Dec2HexFn));
4451    crate::function_registry::register_function(Arc::new(Oct2DecFn));
4452    crate::function_registry::register_function(Arc::new(Dec2OctFn));
4453    crate::function_registry::register_function(Arc::new(Bin2HexFn));
4454    crate::function_registry::register_function(Arc::new(Hex2BinFn));
4455    crate::function_registry::register_function(Arc::new(Bin2OctFn));
4456    crate::function_registry::register_function(Arc::new(Oct2BinFn));
4457    crate::function_registry::register_function(Arc::new(Hex2OctFn));
4458    crate::function_registry::register_function(Arc::new(Oct2HexFn));
4459    crate::function_registry::register_function(Arc::new(DeltaFn));
4460    crate::function_registry::register_function(Arc::new(GestepFn));
4461    crate::function_registry::register_function(Arc::new(ErfFn));
4462    crate::function_registry::register_function(Arc::new(ErfcFn));
4463    crate::function_registry::register_function(Arc::new(ErfPreciseFn));
4464    // Complex number functions
4465    crate::function_registry::register_function(Arc::new(ComplexFn));
4466    crate::function_registry::register_function(Arc::new(ImRealFn));
4467    crate::function_registry::register_function(Arc::new(ImaginaryFn));
4468    crate::function_registry::register_function(Arc::new(ImAbsFn));
4469    crate::function_registry::register_function(Arc::new(ImArgumentFn));
4470    crate::function_registry::register_function(Arc::new(ImConjugateFn));
4471    crate::function_registry::register_function(Arc::new(ImSumFn));
4472    crate::function_registry::register_function(Arc::new(ImSubFn));
4473    crate::function_registry::register_function(Arc::new(ImProductFn));
4474    crate::function_registry::register_function(Arc::new(ImDivFn));
4475    // Complex number math functions
4476    crate::function_registry::register_function(Arc::new(ImExpFn));
4477    crate::function_registry::register_function(Arc::new(ImLnFn));
4478    crate::function_registry::register_function(Arc::new(ImLog10Fn));
4479    crate::function_registry::register_function(Arc::new(ImLog2Fn));
4480    crate::function_registry::register_function(Arc::new(ImPowerFn));
4481    crate::function_registry::register_function(Arc::new(ImSqrtFn));
4482    crate::function_registry::register_function(Arc::new(ImSinFn));
4483    crate::function_registry::register_function(Arc::new(ImCosFn));
4484    // Unit conversion
4485    crate::function_registry::register_function(Arc::new(ConvertFn));
4486}