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}