Skip to main content

sheetkit_core/formula/functions/
engineering.rs

1//! Engineering formula functions: base conversion (BIN2DEC, BIN2HEX, etc.),
2//! complex numbers (COMPLEX, IMREAL, IMAGINARY, etc.), DELTA, GESTEP,
3//! ERF, ERFC, CONVERT, and Bessel functions.
4
5use crate::cell::CellValue;
6use crate::error::Result;
7use crate::formula::ast::Expr;
8use crate::formula::eval::{coerce_to_number, coerce_to_string, Evaluator};
9use crate::formula::functions::check_arg_count;
10
11/// BIN2DEC(number)
12pub fn fn_bin2dec(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
13    check_arg_count("BIN2DEC", args, 1, 1)?;
14    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
15    let s = s.trim();
16    if s.len() > 10 || s.is_empty() || s.chars().any(|c| c != '0' && c != '1') {
17        return Ok(CellValue::Error("#NUM!".to_string()));
18    }
19    let val = i64::from_str_radix(s, 2).unwrap_or(0);
20    let result = if s.len() == 10 && s.starts_with('1') {
21        val - 1024
22    } else {
23        val
24    };
25    Ok(CellValue::Number(result as f64))
26}
27
28/// BIN2HEX(number, [places])
29pub fn fn_bin2hex(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
30    check_arg_count("BIN2HEX", args, 1, 2)?;
31    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
32    let s = s.trim();
33    if s.len() > 10 || s.is_empty() || s.chars().any(|c| c != '0' && c != '1') {
34        return Ok(CellValue::Error("#NUM!".to_string()));
35    }
36    let val = i64::from_str_radix(s, 2).unwrap_or(0);
37    let result = if s.len() == 10 && s.starts_with('1') {
38        val - 1024
39    } else {
40        val
41    };
42    format_hex(result, args, ctx, 1)
43}
44
45/// BIN2OCT(number, [places])
46pub fn fn_bin2oct(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
47    check_arg_count("BIN2OCT", args, 1, 2)?;
48    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
49    let s = s.trim();
50    if s.len() > 10 || s.is_empty() || s.chars().any(|c| c != '0' && c != '1') {
51        return Ok(CellValue::Error("#NUM!".to_string()));
52    }
53    let val = i64::from_str_radix(s, 2).unwrap_or(0);
54    let result = if s.len() == 10 && s.starts_with('1') {
55        val - 1024
56    } else {
57        val
58    };
59    format_oct(result, args, ctx, 1)
60}
61
62/// DEC2BIN(number, [places])
63pub fn fn_dec2bin(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
64    check_arg_count("DEC2BIN", args, 1, 2)?;
65    let n = coerce_to_number(&ctx.eval_expr(&args[0])?)? as i64;
66    if !(-512..=511).contains(&n) {
67        return Ok(CellValue::Error("#NUM!".to_string()));
68    }
69    format_bin(n, args, ctx, 1)
70}
71
72/// DEC2HEX(number, [places])
73pub fn fn_dec2hex(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
74    check_arg_count("DEC2HEX", args, 1, 2)?;
75    let n = coerce_to_number(&ctx.eval_expr(&args[0])?)? as i64;
76    if !(-549_755_813_888..=549_755_813_887).contains(&n) {
77        return Ok(CellValue::Error("#NUM!".to_string()));
78    }
79    format_hex(n, args, ctx, 1)
80}
81
82/// DEC2OCT(number, [places])
83pub fn fn_dec2oct(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
84    check_arg_count("DEC2OCT", args, 1, 2)?;
85    let n = coerce_to_number(&ctx.eval_expr(&args[0])?)? as i64;
86    if !(-536_870_912..=536_870_911).contains(&n) {
87        return Ok(CellValue::Error("#NUM!".to_string()));
88    }
89    format_oct(n, args, ctx, 1)
90}
91
92/// HEX2BIN(number, [places])
93pub fn fn_hex2bin(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
94    check_arg_count("HEX2BIN", args, 1, 2)?;
95    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
96    let s = s.trim();
97    if s.len() > 10 || s.is_empty() || !s.chars().all(|c| c.is_ascii_hexdigit()) {
98        return Ok(CellValue::Error("#NUM!".to_string()));
99    }
100    let val = i64::from_str_radix(s, 16).unwrap_or(0);
101    let result = if val > 0x7FFFFFFFFF {
102        val - 0x10000000000_i64
103    } else {
104        val
105    };
106    if !(-512..=511).contains(&result) {
107        return Ok(CellValue::Error("#NUM!".to_string()));
108    }
109    format_bin(result, args, ctx, 1)
110}
111
112/// HEX2DEC(number)
113pub fn fn_hex2dec(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
114    check_arg_count("HEX2DEC", args, 1, 1)?;
115    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
116    let s = s.trim();
117    if s.len() > 10 || s.is_empty() || !s.chars().all(|c| c.is_ascii_hexdigit()) {
118        return Ok(CellValue::Error("#NUM!".to_string()));
119    }
120    let val = i64::from_str_radix(s, 16).unwrap_or(0);
121    let result = if val > 0x7FFFFFFFFF {
122        val - 0x10000000000_i64
123    } else {
124        val
125    };
126    Ok(CellValue::Number(result as f64))
127}
128
129/// HEX2OCT(number, [places])
130pub fn fn_hex2oct(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
131    check_arg_count("HEX2OCT", args, 1, 2)?;
132    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
133    let s = s.trim();
134    if s.len() > 10 || s.is_empty() || !s.chars().all(|c| c.is_ascii_hexdigit()) {
135        return Ok(CellValue::Error("#NUM!".to_string()));
136    }
137    let val = i64::from_str_radix(s, 16).unwrap_or(0);
138    let result = if val > 0x7FFFFFFFFF {
139        val - 0x10000000000_i64
140    } else {
141        val
142    };
143    if !(-536_870_912..=536_870_911).contains(&result) {
144        return Ok(CellValue::Error("#NUM!".to_string()));
145    }
146    format_oct(result, args, ctx, 1)
147}
148
149/// OCT2BIN(number, [places])
150pub fn fn_oct2bin(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
151    check_arg_count("OCT2BIN", args, 1, 2)?;
152    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
153    let s = s.trim();
154    if s.len() > 10 || s.is_empty() || s.chars().any(|c| !('0'..='7').contains(&c)) {
155        return Ok(CellValue::Error("#NUM!".to_string()));
156    }
157    let val = i64::from_str_radix(s, 8).unwrap_or(0);
158    // 30-bit two's complement: values above 2^29-1 are negative.
159    let result = if val > 0x1FFFFFFF {
160        val - 0x40000000
161    } else {
162        val
163    };
164    if !(-512..=511).contains(&result) {
165        return Ok(CellValue::Error("#NUM!".to_string()));
166    }
167    format_bin(result, args, ctx, 1)
168}
169
170/// OCT2DEC(number)
171pub fn fn_oct2dec(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
172    check_arg_count("OCT2DEC", args, 1, 1)?;
173    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
174    let s = s.trim();
175    if s.len() > 10 || s.is_empty() || s.chars().any(|c| !('0'..='7').contains(&c)) {
176        return Ok(CellValue::Error("#NUM!".to_string()));
177    }
178    let val = i64::from_str_radix(s, 8).unwrap_or(0);
179    // 30-bit two's complement: values above 2^29-1 are negative.
180    let result = if val > 0x1FFFFFFF {
181        val - 0x40000000
182    } else {
183        val
184    };
185    Ok(CellValue::Number(result as f64))
186}
187
188/// OCT2HEX(number, [places])
189pub fn fn_oct2hex(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
190    check_arg_count("OCT2HEX", args, 1, 2)?;
191    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
192    let s = s.trim();
193    if s.len() > 10 || s.is_empty() || s.chars().any(|c| !('0'..='7').contains(&c)) {
194        return Ok(CellValue::Error("#NUM!".to_string()));
195    }
196    let val = i64::from_str_radix(s, 8).unwrap_or(0);
197    // 30-bit two's complement: values above 2^29-1 are negative.
198    let result = if val > 0x1FFFFFFF {
199        val - 0x40000000
200    } else {
201        val
202    };
203    format_hex(result, args, ctx, 1)
204}
205
206/// DELTA(number1, [number2])
207pub fn fn_delta(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
208    check_arg_count("DELTA", args, 1, 2)?;
209    let n1 = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
210    let n2 = if args.len() > 1 {
211        coerce_to_number(&ctx.eval_expr(&args[1])?)?
212    } else {
213        0.0
214    };
215    // Excel DELTA uses exact bitwise equality, not epsilon comparison.
216    Ok(CellValue::Number(if n1 == n2 { 1.0 } else { 0.0 }))
217}
218
219/// GESTEP(number, [step])
220pub fn fn_gestep(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
221    check_arg_count("GESTEP", args, 1, 2)?;
222    let n = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
223    let step = if args.len() > 1 {
224        coerce_to_number(&ctx.eval_expr(&args[1])?)?
225    } else {
226        0.0
227    };
228    Ok(CellValue::Number(if n >= step { 1.0 } else { 0.0 }))
229}
230
231/// ERF(lower_limit, [upper_limit])
232pub fn fn_erf(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
233    check_arg_count("ERF", args, 1, 2)?;
234    let lower = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
235    if args.len() > 1 {
236        let upper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
237        Ok(CellValue::Number(erf_approx(upper) - erf_approx(lower)))
238    } else {
239        Ok(CellValue::Number(erf_approx(lower)))
240    }
241}
242
243/// ERFC(x)
244pub fn fn_erfc(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
245    check_arg_count("ERFC", args, 1, 1)?;
246    let x = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
247    Ok(CellValue::Number(1.0 - erf_approx(x)))
248}
249
250/// COMPLEX(real_num, i_num, [suffix])
251pub fn fn_complex(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
252    check_arg_count("COMPLEX", args, 2, 3)?;
253    let real = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
254    let imag = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
255    let suffix = if args.len() > 2 {
256        let s = coerce_to_string(&ctx.eval_expr(&args[2])?);
257        if s != "i" && s != "j" {
258            return Ok(CellValue::Error("#VALUE!".to_string()));
259        }
260        s
261    } else {
262        "i".to_string()
263    };
264    Ok(CellValue::String(format_complex(real, imag, &suffix)))
265}
266
267/// IMREAL(inumber)
268pub fn fn_imreal(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
269    check_arg_count("IMREAL", args, 1, 1)?;
270    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
271    match parse_complex(&s) {
272        Some((real, _)) => Ok(CellValue::Number(real)),
273        None => Ok(CellValue::Error("#NUM!".to_string())),
274    }
275}
276
277/// IMAGINARY(inumber)
278pub fn fn_imaginary(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
279    check_arg_count("IMAGINARY", args, 1, 1)?;
280    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
281    match parse_complex(&s) {
282        Some((_, imag)) => Ok(CellValue::Number(imag)),
283        None => Ok(CellValue::Error("#NUM!".to_string())),
284    }
285}
286
287/// IMABS(inumber)
288pub fn fn_imabs(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
289    check_arg_count("IMABS", args, 1, 1)?;
290    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
291    match parse_complex(&s) {
292        Some((r, i)) => Ok(CellValue::Number((r * r + i * i).sqrt())),
293        None => Ok(CellValue::Error("#NUM!".to_string())),
294    }
295}
296
297/// IMARGUMENT(inumber)
298pub fn fn_imargument(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
299    check_arg_count("IMARGUMENT", args, 1, 1)?;
300    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
301    match parse_complex(&s) {
302        Some((r, i)) => {
303            if r == 0.0 && i == 0.0 {
304                return Ok(CellValue::Error("#DIV/0!".to_string()));
305            }
306            Ok(CellValue::Number(i.atan2(r)))
307        }
308        None => Ok(CellValue::Error("#NUM!".to_string())),
309    }
310}
311
312/// IMCONJUGATE(inumber)
313pub fn fn_imconjugate(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
314    check_arg_count("IMCONJUGATE", args, 1, 1)?;
315    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
316    match parse_complex(&s) {
317        Some((r, i)) => Ok(CellValue::String(format_complex(r, -i, "i"))),
318        None => Ok(CellValue::Error("#NUM!".to_string())),
319    }
320}
321
322/// IMSUM(inumber1, [inumber2], ...)
323pub fn fn_imsum(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
324    check_arg_count("IMSUM", args, 1, 255)?;
325    let mut real_sum = 0.0;
326    let mut imag_sum = 0.0;
327    let values = ctx.flatten_args_to_values(args)?;
328    for v in &values {
329        let s = coerce_to_string(v);
330        match parse_complex(&s) {
331            Some((r, i)) => {
332                real_sum += r;
333                imag_sum += i;
334            }
335            None => return Ok(CellValue::Error("#NUM!".to_string())),
336        }
337    }
338    Ok(CellValue::String(format_complex(real_sum, imag_sum, "i")))
339}
340
341/// IMSUB(inumber1, inumber2)
342pub fn fn_imsub(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
343    check_arg_count("IMSUB", args, 2, 2)?;
344    let s1 = coerce_to_string(&ctx.eval_expr(&args[0])?);
345    let s2 = coerce_to_string(&ctx.eval_expr(&args[1])?);
346    match (parse_complex(&s1), parse_complex(&s2)) {
347        (Some((r1, i1)), Some((r2, i2))) => {
348            Ok(CellValue::String(format_complex(r1 - r2, i1 - i2, "i")))
349        }
350        _ => Ok(CellValue::Error("#NUM!".to_string())),
351    }
352}
353
354/// IMPRODUCT(inumber1, [inumber2], ...)
355pub fn fn_improduct(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
356    check_arg_count("IMPRODUCT", args, 1, 255)?;
357    let values = ctx.flatten_args_to_values(args)?;
358    let mut real = 1.0;
359    let mut imag = 0.0;
360    for v in &values {
361        let s = coerce_to_string(v);
362        match parse_complex(&s) {
363            Some((r, i)) => {
364                let new_real = real * r - imag * i;
365                let new_imag = real * i + imag * r;
366                real = new_real;
367                imag = new_imag;
368            }
369            None => return Ok(CellValue::Error("#NUM!".to_string())),
370        }
371    }
372    Ok(CellValue::String(format_complex(real, imag, "i")))
373}
374
375/// IMDIV(inumber1, inumber2)
376pub fn fn_imdiv(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
377    check_arg_count("IMDIV", args, 2, 2)?;
378    let s1 = coerce_to_string(&ctx.eval_expr(&args[0])?);
379    let s2 = coerce_to_string(&ctx.eval_expr(&args[1])?);
380    match (parse_complex(&s1), parse_complex(&s2)) {
381        (Some((r1, i1)), Some((r2, i2))) => {
382            let denom = r2 * r2 + i2 * i2;
383            if denom == 0.0 {
384                return Ok(CellValue::Error("#NUM!".to_string()));
385            }
386            let real = (r1 * r2 + i1 * i2) / denom;
387            let imag = (i1 * r2 - r1 * i2) / denom;
388            Ok(CellValue::String(format_complex(real, imag, "i")))
389        }
390        _ => Ok(CellValue::Error("#NUM!".to_string())),
391    }
392}
393
394/// IMPOWER(inumber, number)
395pub fn fn_impower(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
396    check_arg_count("IMPOWER", args, 2, 2)?;
397    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
398    let n = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
399    match parse_complex(&s) {
400        Some((r, i)) => {
401            let modulus = (r * r + i * i).sqrt();
402            if modulus == 0.0 {
403                if n > 0.0 {
404                    return Ok(CellValue::String("0".to_string()));
405                }
406                return Ok(CellValue::Error("#NUM!".to_string()));
407            }
408            let arg = i.atan2(r);
409            let new_mod = modulus.powf(n);
410            let new_arg = arg * n;
411            let real = new_mod * new_arg.cos();
412            let imag = new_mod * new_arg.sin();
413            Ok(CellValue::String(format_complex(real, imag, "i")))
414        }
415        None => Ok(CellValue::Error("#NUM!".to_string())),
416    }
417}
418
419/// IMSQRT(inumber)
420pub fn fn_imsqrt(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
421    check_arg_count("IMSQRT", args, 1, 1)?;
422    let s = coerce_to_string(&ctx.eval_expr(&args[0])?);
423    match parse_complex(&s) {
424        Some((r, i)) => {
425            let modulus = (r * r + i * i).sqrt();
426            let arg = i.atan2(r);
427            let new_mod = modulus.sqrt();
428            let new_arg = arg / 2.0;
429            let real = new_mod * new_arg.cos();
430            let imag = new_mod * new_arg.sin();
431            Ok(CellValue::String(format_complex(real, imag, "i")))
432        }
433        None => Ok(CellValue::Error("#NUM!".to_string())),
434    }
435}
436
437/// CONVERT(number, from_unit, to_unit)
438pub fn fn_convert(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
439    check_arg_count("CONVERT", args, 3, 3)?;
440    let n = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
441    let from = coerce_to_string(&ctx.eval_expr(&args[1])?);
442    let to = coerce_to_string(&ctx.eval_expr(&args[2])?);
443    match convert_units(n, &from, &to) {
444        Some(result) => Ok(CellValue::Number(result)),
445        None => Ok(CellValue::Error("#N/A".to_string())),
446    }
447}
448
449/// BESSELI(x, n)
450pub fn fn_besseli(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
451    check_arg_count("BESSELI", args, 2, 2)?;
452    let x = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
453    let n = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
454    if n < 0 {
455        return Ok(CellValue::Error("#NUM!".to_string()));
456    }
457    Ok(CellValue::Number(bessel_i(x, n as f64)))
458}
459
460/// BESSELJ(x, n)
461pub fn fn_besselj(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
462    check_arg_count("BESSELJ", args, 2, 2)?;
463    let x = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
464    let n = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
465    if n < 0 {
466        return Ok(CellValue::Error("#NUM!".to_string()));
467    }
468    Ok(CellValue::Number(bessel_j(x, n as f64)))
469}
470
471/// BESSELK(x, n)
472pub fn fn_besselk(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
473    check_arg_count("BESSELK", args, 2, 2)?;
474    let x = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
475    let n = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
476    if x <= 0.0 || n < 0 {
477        return Ok(CellValue::Error("#NUM!".to_string()));
478    }
479    Ok(CellValue::Number(bessel_k(x, n)))
480}
481
482/// BESSELY(x, n)
483pub fn fn_bessely(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
484    check_arg_count("BESSELY", args, 2, 2)?;
485    let x = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
486    let n = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
487    if x <= 0.0 || n < 0 {
488        return Ok(CellValue::Error("#NUM!".to_string()));
489    }
490    Ok(CellValue::Number(bessel_y(x, n)))
491}
492
493fn format_bin(n: i64, args: &[Expr], ctx: &mut Evaluator, places_idx: usize) -> Result<CellValue> {
494    let s = if n >= 0 {
495        format!("{n:b}")
496    } else {
497        let bits = (n as u64) & 0x3FF;
498        format!("{bits:010b}")
499    };
500    if args.len() > places_idx {
501        let places = coerce_to_number(&ctx.eval_expr(&args[places_idx])?)? as usize;
502        if places < 1 || (n >= 0 && s.len() > places) {
503            return Ok(CellValue::Error("#NUM!".to_string()));
504        }
505        if n >= 0 {
506            return Ok(CellValue::String(format!("{:0>width$}", s, width = places)));
507        }
508    }
509    Ok(CellValue::String(s))
510}
511
512fn format_hex(n: i64, args: &[Expr], ctx: &mut Evaluator, places_idx: usize) -> Result<CellValue> {
513    let s = if n >= 0 {
514        format!("{n:X}")
515    } else {
516        let bits = (n as u64) & 0xFF_FFFF_FFFF;
517        format!("{bits:010X}")
518    };
519    if args.len() > places_idx {
520        let places = coerce_to_number(&ctx.eval_expr(&args[places_idx])?)? as usize;
521        if places < 1 || (n >= 0 && s.len() > places) {
522            return Ok(CellValue::Error("#NUM!".to_string()));
523        }
524        if n >= 0 {
525            return Ok(CellValue::String(format!("{:0>width$}", s, width = places)));
526        }
527    }
528    Ok(CellValue::String(s))
529}
530
531fn format_oct(n: i64, args: &[Expr], ctx: &mut Evaluator, places_idx: usize) -> Result<CellValue> {
532    let s = if n >= 0 {
533        format!("{n:o}")
534    } else {
535        // 10 octal digits = 30 bits
536        let bits = (n as u64) & 0x3FFFFFFF;
537        format!("{bits:010o}")
538    };
539    if args.len() > places_idx {
540        let places = coerce_to_number(&ctx.eval_expr(&args[places_idx])?)? as usize;
541        if places < 1 || (n >= 0 && s.len() > places) {
542            return Ok(CellValue::Error("#NUM!".to_string()));
543        }
544        if n >= 0 {
545            return Ok(CellValue::String(format!("{:0>width$}", s, width = places)));
546        }
547    }
548    Ok(CellValue::String(s))
549}
550
551fn erf_approx(x: f64) -> f64 {
552    let sign = if x < 0.0 { -1.0 } else { 1.0 };
553    let x = x.abs();
554    let t = 1.0 / (1.0 + 0.3275911 * x);
555    let poly = t
556        * (0.254829592
557            + t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
558    sign * (1.0 - poly * (-x * x).exp())
559}
560
561fn parse_complex(s: &str) -> Option<(f64, f64)> {
562    let s = s.trim();
563    if s.is_empty() {
564        return None;
565    }
566    if let Ok(n) = s.parse::<f64>() {
567        return Some((n, 0.0));
568    }
569    if s == "i" || s == "j" {
570        return Some((0.0, 1.0));
571    }
572    if s == "-i" || s == "-j" {
573        return Some((0.0, -1.0));
574    }
575    if s == "+i" || s == "+j" {
576        return Some((0.0, 1.0));
577    }
578    let suffix = if s.ends_with('i') || s.ends_with('j') {
579        s.len() - 1
580    } else {
581        return None;
582    };
583    let body = &s[..suffix];
584    let sign_pos = body.rfind('+').or_else(|| {
585        let rp = body.rfind('-')?;
586        if rp == 0 {
587            None
588        } else {
589            Some(rp)
590        }
591    });
592    match sign_pos {
593        Some(pos) => {
594            let real_str = &body[..pos];
595            let imag_str = &body[pos..];
596            let real = if real_str.is_empty() {
597                0.0
598            } else {
599                real_str.parse::<f64>().ok()?
600            };
601            let imag = if imag_str == "+" || imag_str.is_empty() {
602                1.0
603            } else if imag_str == "-" {
604                -1.0
605            } else {
606                imag_str.parse::<f64>().ok()?
607            };
608            Some((real, imag))
609        }
610        None => {
611            let imag = if body == "+" || body.is_empty() {
612                1.0
613            } else if body == "-" {
614                -1.0
615            } else {
616                body.parse::<f64>().ok()?
617            };
618            Some((0.0, imag))
619        }
620    }
621}
622
623fn format_complex(real: f64, imag: f64, suffix: &str) -> String {
624    let real = clean_float(real);
625    let imag = clean_float(imag);
626    if imag == 0.0 {
627        return format_number(real);
628    }
629    if real == 0.0 {
630        if imag == 1.0 {
631            return suffix.to_string();
632        }
633        if imag == -1.0 {
634            return format!("-{suffix}");
635        }
636        return format!("{}{suffix}", format_number(imag));
637    }
638    let imag_str = if imag == 1.0 {
639        format!("+{suffix}")
640    } else if imag == -1.0 {
641        format!("-{suffix}")
642    } else if imag > 0.0 {
643        format!("+{}{suffix}", format_number(imag))
644    } else {
645        format!("{}{suffix}", format_number(imag))
646    };
647    format!("{}{imag_str}", format_number(real))
648}
649
650fn clean_float(v: f64) -> f64 {
651    if v.abs() < 1e-15 {
652        0.0
653    } else {
654        v
655    }
656}
657
658fn format_number(n: f64) -> String {
659    if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
660        format!("{}", n as i64)
661    } else {
662        format!("{n}")
663    }
664}
665
666fn convert_units(value: f64, from: &str, to: &str) -> Option<f64> {
667    let from_base = unit_to_base_factor(from)?;
668    let to_base = unit_to_base_factor(to)?;
669    if from_base.1 != to_base.1 {
670        if from_base.1 == "C" && to_base.1 == "C" {
671            return None;
672        }
673        return None;
674    }
675    if from_base.1 == "C" {
676        let celsius = temp_to_celsius(value, from)?;
677        return celsius_to_temp(celsius, to);
678    }
679    Some(value * from_base.0 / to_base.0)
680}
681
682fn temp_to_celsius(value: f64, unit: &str) -> Option<f64> {
683    match unit {
684        "C" | "cel" => Some(value),
685        "F" | "fah" => Some((value - 32.0) * 5.0 / 9.0),
686        "K" | "kel" => Some(value - 273.15),
687        "Rank" => Some((value - 491.67) * 5.0 / 9.0),
688        "Reau" => Some(value * 5.0 / 4.0),
689        _ => None,
690    }
691}
692
693fn celsius_to_temp(celsius: f64, unit: &str) -> Option<f64> {
694    match unit {
695        "C" | "cel" => Some(celsius),
696        "F" | "fah" => Some(celsius * 9.0 / 5.0 + 32.0),
697        "K" | "kel" => Some(celsius + 273.15),
698        "Rank" => Some((celsius + 273.15) * 9.0 / 5.0),
699        "Reau" => Some(celsius * 4.0 / 5.0),
700        _ => None,
701    }
702}
703
704fn unit_to_base_factor(unit: &str) -> Option<(f64, &'static str)> {
705    match unit {
706        // Weight/Mass (base: kg)
707        "g" => Some((0.001, "mass")),
708        "kg" => Some((1.0, "mass")),
709        "mg" => Some((1e-6, "mass")),
710        "lbm" => Some((0.45359237, "mass")),
711        "ozm" => Some((0.028349523125, "mass")),
712        "stone" => Some((6.35029318, "mass")),
713        "ton" => Some((907.18474, "mass")),
714        "sg" => Some((14.593903, "mass")),
715        "u" => Some((1.66053906660e-27, "mass")),
716        "grain" => Some((6.479891e-5, "mass")),
717        "cwt" | "shweight" => Some((45.359237, "mass")),
718        "uk_cwt" | "lcwt" | "hweight" => Some((50.80234544, "mass")),
719        "LTON" | "brton" => Some((1016.0469088, "mass")),
720
721        // Distance (base: m)
722        "m" => Some((1.0, "dist")),
723        "km" => Some((1000.0, "dist")),
724        "cm" => Some((0.01, "dist")),
725        "mm" => Some((0.001, "dist")),
726        "mi" => Some((1609.344, "dist")),
727        "Nmi" => Some((1852.0, "dist")),
728        "in" => Some((0.0254, "dist")),
729        "ft" => Some((0.3048, "dist")),
730        "yd" => Some((0.9144, "dist")),
731        "ang" => Some((1e-10, "dist")),
732        "ell" => Some((1.143, "dist")),
733        "ly" => Some((9.46073047258e15, "dist")),
734        "parsec" | "pc" => Some((3.08567758149e16, "dist")),
735        "Pica" | "Picapt" => Some((0.00035277778, "dist")),
736        "pica" => Some((0.00423333333, "dist")),
737        "survey_mi" => Some((1609.3472, "dist")),
738
739        // Time (base: s)
740        "sec" | "s" => Some((1.0, "time")),
741        "min" | "mn" => Some((60.0, "time")),
742        "hr" => Some((3600.0, "time")),
743        "day" | "d" => Some((86400.0, "time")),
744        "yr" => Some((365.25 * 86400.0, "time")),
745
746        // Speed (base: m/s)
747        "m/s" | "m/sec" => Some((1.0, "speed")),
748        "m/h" | "m/hr" => Some((1.0 / 3600.0, "speed")),
749        "mph" => Some((0.44704, "speed")),
750        "kn" | "admkn" => Some((0.514444444, "speed")),
751
752        // Area (base: m^2)
753        "ar" => Some((100.0, "area")),
754        "ha" => Some((10000.0, "area")),
755        "uk_acre" => Some((4046.8564224, "area")),
756        "us_acre" => Some((4046.8726, "area")),
757
758        // Volume (base: l)
759        "l" | "L" | "lt" => Some((1.0, "vol")),
760        "ml" => Some((0.001, "vol")),
761        "gal" => Some((3.78541178, "vol")),
762        "qt" => Some((0.946352946, "vol")),
763        "pt" | "us_pt" => Some((0.473176473, "vol")),
764        "cup" => Some((0.236588236, "vol")),
765        "oz" | "fl_oz" | "us_oz" => Some((0.0295735296, "vol")),
766        "tbs" => Some((0.0147867648, "vol")),
767        "tsp" => Some((0.00492892159, "vol")),
768        "uk_gal" => Some((4.54609, "vol")),
769        "uk_qt" => Some((1.1365225, "vol")),
770        "uk_pt" => Some((0.56826125, "vol")),
771
772        // Energy (base: J)
773        "J" | "j" => Some((1.0, "energy")),
774        "e" => Some((1e-7, "energy")),
775        "cal" => Some((4.1868, "energy")),
776        "eV" | "ev" => Some((1.602176634e-19, "energy")),
777        "HPh" | "hh" => Some((2684519.5, "energy")),
778        "Wh" | "wh" => Some((3600.0, "energy")),
779        "flb" => Some((1.3558179483, "energy")),
780        "BTU" | "btu" => Some((1055.05585262, "energy")),
781
782        // Power (base: W)
783        "W" | "w" => Some((1.0, "power")),
784        "kW" | "kw" => Some((1000.0, "power")),
785        "HP" | "h" => Some((745.69987158, "power")),
786        "PS" => Some((735.49875, "power")),
787
788        // Force (base: N)
789        "N" => Some((1.0, "force")),
790        "dyn" | "dy" => Some((1e-5, "force")),
791        "lbf" => Some((4.4482216152605, "force")),
792        "pond" => Some((9.80665e-3, "force")),
793
794        // Pressure (base: Pa)
795        "Pa" | "p" => Some((1.0, "press")),
796        "atm" | "at" => Some((101325.0, "press")),
797        "mmHg" => Some((133.322, "press")),
798        "psi" => Some((6894.757, "press")),
799        "Torr" => Some((133.3224, "press")),
800
801        // Temperature (special handling)
802        "C" | "cel" | "F" | "fah" | "K" | "kel" | "Rank" | "Reau" => Some((1.0, "C")),
803
804        // Information (base: bit)
805        "bit" => Some((1.0, "info")),
806        "byte" => Some((8.0, "info")),
807
808        _ => None,
809    }
810}
811
812fn bessel_y(x: f64, n: i32) -> f64 {
813    // For integer orders, the generic formula divides by sin(n*pi) = 0.
814    // Use the limiting form via numerical differentiation instead.
815    let nf = n as f64;
816    let eps = 1e-8;
817    let y_plus = {
818        let v = nf + eps;
819        let pi = std::f64::consts::PI;
820        ((v * pi).cos() * bessel_j(x, v) - bessel_j(x, -v)) / (v * pi).sin()
821    };
822    let y_minus = {
823        let v = nf - eps;
824        let pi = std::f64::consts::PI;
825        ((v * pi).cos() * bessel_j(x, v) - bessel_j(x, -v)) / (v * pi).sin()
826    };
827    (y_plus + y_minus) / 2.0
828}
829
830/// Evaluate J_v(x) for real (non-integer) order v using the power series.
831fn bessel_j(x: f64, v: f64) -> f64 {
832    let mut sum = 0.0;
833    for m in 0_i32..50 {
834        let sign = if m % 2 == 0 { 1.0 } else { -1.0 };
835        let numer = (x / 2.0).powf(2.0 * m as f64 + v);
836        let denom = gamma(m as f64 + 1.0) * gamma(m as f64 + v + 1.0);
837        if denom == 0.0 || !numer.is_finite() {
838            break;
839        }
840        sum += sign * numer / denom;
841    }
842    sum
843}
844
845fn bessel_k(x: f64, n: i32) -> f64 {
846    // For integer orders, the generic formula divides by sin(n*pi) = 0.
847    // Use the limiting form via numerical differentiation.
848    let nf = n as f64;
849    let eps = 1e-8;
850    let pi = std::f64::consts::PI;
851    let k_plus = {
852        let v = nf + eps;
853        pi / 2.0 * (bessel_i(x, -v) - bessel_i(x, v)) / (v * pi).sin()
854    };
855    let k_minus = {
856        let v = nf - eps;
857        pi / 2.0 * (bessel_i(x, -v) - bessel_i(x, v)) / (v * pi).sin()
858    };
859    (k_plus + k_minus) / 2.0
860}
861
862/// Evaluate I_v(x) for real (non-integer) order v using the power series.
863fn bessel_i(x: f64, v: f64) -> f64 {
864    let mut sum = 0.0;
865    for m in 0_i32..50 {
866        let numer = (x / 2.0).powf(2.0 * m as f64 + v);
867        let denom = gamma(m as f64 + 1.0) * gamma(m as f64 + v + 1.0);
868        if denom == 0.0 || !numer.is_finite() {
869            break;
870        }
871        sum += numer / denom;
872    }
873    sum
874}
875
876/// Lanczos approximation of the Gamma function for real arguments.
877fn gamma(z: f64) -> f64 {
878    if z < 0.5 {
879        let pi = std::f64::consts::PI;
880        return pi / ((pi * z).sin() * gamma(1.0 - z));
881    }
882    let g = 7.0;
883    #[allow(clippy::excessive_precision)]
884    let c = [
885        0.99999999999980993,
886        676.5203681218851,
887        -1259.1392167224028,
888        771.32342877765313,
889        -176.61502916214059,
890        12.507343278686905,
891        -0.13857109526572012,
892        9.9843695780195716e-6,
893        1.5056327351493116e-7,
894    ];
895    let z = z - 1.0;
896    let mut x = c[0];
897    for (i, &coeff) in c.iter().enumerate().skip(1) {
898        x += coeff / (z + i as f64);
899    }
900    let t = z + g + 0.5;
901    (2.0 * std::f64::consts::PI).sqrt() * t.powf(z + 0.5) * (-t).exp() * x
902}
903
904#[cfg(test)]
905mod tests {
906    use crate::cell::CellValue;
907    use crate::formula::eval::{evaluate, CellSnapshot};
908    use crate::formula::parser::parse_formula;
909
910    fn eval(formula: &str) -> CellValue {
911        let snap = CellSnapshot::new("Sheet1".to_string());
912        let expr = parse_formula(formula).unwrap();
913        evaluate(&expr, &snap).unwrap()
914    }
915
916    fn assert_approx(result: CellValue, expected: f64, tol: f64) {
917        match result {
918            CellValue::Number(n) => {
919                assert!((n - expected).abs() < tol, "expected ~{expected}, got {n}");
920            }
921            other => panic!("expected number ~{expected}, got {other:?}"),
922        }
923    }
924
925    #[test]
926    fn bin2dec_positive() {
927        assert_approx(eval("BIN2DEC(\"1100100\")"), 100.0, 0.01);
928    }
929
930    #[test]
931    fn bin2dec_negative() {
932        assert_approx(eval("BIN2DEC(\"1111111111\")"), -1.0, 0.01);
933    }
934
935    #[test]
936    fn bin2hex_basic() {
937        assert_eq!(
938            eval("BIN2HEX(\"11111011\",4)"),
939            CellValue::String("00FB".to_string())
940        );
941    }
942
943    #[test]
944    fn bin2oct_basic() {
945        assert_eq!(
946            eval("BIN2OCT(\"1001\",4)"),
947            CellValue::String("0011".to_string())
948        );
949    }
950
951    #[test]
952    fn dec2bin_basic() {
953        assert_eq!(eval("DEC2BIN(9)"), CellValue::String("1001".to_string()));
954    }
955
956    #[test]
957    fn dec2bin_negative() {
958        assert_eq!(
959            eval("DEC2BIN(-100)"),
960            CellValue::String("1110011100".to_string())
961        );
962    }
963
964    #[test]
965    fn dec2bin_with_places() {
966        assert_eq!(
967            eval("DEC2BIN(9,8)"),
968            CellValue::String("00001001".to_string())
969        );
970    }
971
972    #[test]
973    fn dec2hex_basic() {
974        assert_eq!(eval("DEC2HEX(100)"), CellValue::String("64".to_string()));
975    }
976
977    #[test]
978    fn dec2hex_negative() {
979        let result = eval("DEC2HEX(-54)");
980        assert_eq!(result, CellValue::String("FFFFFFFFCA".to_string()));
981    }
982
983    #[test]
984    fn dec2oct_basic() {
985        assert_eq!(eval("DEC2OCT(58)"), CellValue::String("72".to_string()));
986    }
987
988    #[test]
989    fn dec2oct_negative() {
990        // DEC2OCT(-1) should produce 7777777777 (10-digit, 30-bit two's complement)
991        assert_eq!(
992            eval("DEC2OCT(-1)"),
993            CellValue::String("7777777777".to_string())
994        );
995        // DEC2OCT(-536870912) is the minimum value
996        assert_eq!(
997            eval("DEC2OCT(-536870912)"),
998            CellValue::String("4000000000".to_string())
999        );
1000    }
1001
1002    #[test]
1003    fn hex2bin_basic() {
1004        assert_eq!(
1005            eval("HEX2BIN(\"F\",8)"),
1006            CellValue::String("00001111".to_string())
1007        );
1008    }
1009
1010    #[test]
1011    fn hex2dec_basic() {
1012        assert_approx(eval("HEX2DEC(\"A5\")"), 165.0, 0.01);
1013    }
1014
1015    #[test]
1016    fn hex2dec_negative() {
1017        assert_approx(eval("HEX2DEC(\"FFFFFFFFFF\")"), -1.0, 0.01);
1018    }
1019
1020    #[test]
1021    fn hex2oct_basic() {
1022        assert_eq!(
1023            eval("HEX2OCT(\"F\",3)"),
1024            CellValue::String("017".to_string())
1025        );
1026    }
1027
1028    #[test]
1029    fn oct2bin_basic() {
1030        assert_eq!(
1031            eval("OCT2BIN(\"3\",4)"),
1032            CellValue::String("0011".to_string())
1033        );
1034    }
1035
1036    #[test]
1037    fn oct2bin_negative() {
1038        // 7777777000 octal = -512 decimal -> 10-bit binary 1000000000
1039        assert_eq!(
1040            eval("OCT2BIN(\"7777777000\")"),
1041            CellValue::String("1000000000".to_string())
1042        );
1043        // 7777777776 octal = -2 decimal -> 1111111110
1044        assert_eq!(
1045            eval("OCT2BIN(\"7777777776\")"),
1046            CellValue::String("1111111110".to_string())
1047        );
1048        // 7777777777 octal = -1 decimal -> 1111111111
1049        assert_eq!(
1050            eval("OCT2BIN(\"7777777777\")"),
1051            CellValue::String("1111111111".to_string())
1052        );
1053    }
1054
1055    #[test]
1056    fn oct2dec_basic() {
1057        assert_approx(eval("OCT2DEC(\"54\")"), 44.0, 0.01);
1058    }
1059
1060    #[test]
1061    fn oct2dec_negative() {
1062        // 7777777777 octal = -1 decimal (30-bit two's complement)
1063        assert_approx(eval("OCT2DEC(\"7777777777\")"), -1.0, 0.01);
1064        // 4000000000 octal = -536870912 decimal
1065        assert_approx(eval("OCT2DEC(\"4000000000\")"), -536_870_912.0, 0.01);
1066        // 7777777000 octal = -512 decimal
1067        assert_approx(eval("OCT2DEC(\"7777777000\")"), -512.0, 0.01);
1068    }
1069
1070    #[test]
1071    fn oct2dec_positive_boundary() {
1072        // 3777777777 octal = 536870911 (max positive in 30-bit two's complement)
1073        assert_approx(eval("OCT2DEC(\"3777777777\")"), 536_870_911.0, 0.01);
1074    }
1075
1076    #[test]
1077    fn oct2hex_basic() {
1078        assert_eq!(
1079            eval("OCT2HEX(\"100\",4)"),
1080            CellValue::String("0040".to_string())
1081        );
1082    }
1083
1084    #[test]
1085    fn oct2hex_negative() {
1086        // 7777777777 octal = -1 decimal -> FFFFFFFFFF hex
1087        assert_eq!(
1088            eval("OCT2HEX(\"7777777777\")"),
1089            CellValue::String("FFFFFFFFFF".to_string())
1090        );
1091        // 4000000000 octal = -536870912 decimal -> FFE0000000 hex
1092        assert_eq!(
1093            eval("OCT2HEX(\"4000000000\")"),
1094            CellValue::String("FFE0000000".to_string())
1095        );
1096    }
1097
1098    #[test]
1099    fn delta_equal() {
1100        assert_approx(eval("DELTA(5,5)"), 1.0, 0.01);
1101    }
1102
1103    #[test]
1104    fn delta_not_equal() {
1105        assert_approx(eval("DELTA(5,4)"), 0.0, 0.01);
1106    }
1107
1108    #[test]
1109    fn delta_default() {
1110        assert_approx(eval("DELTA(0)"), 1.0, 0.01);
1111    }
1112
1113    #[test]
1114    fn delta_distinct_close_values() {
1115        // Values that differ by more than f64::EPSILON but are distinct
1116        // should NOT be considered equal (old code used epsilon comparison).
1117        // 1.0 and 1.0 + 1e-15 are distinct f64 values.
1118        let a = 1.0_f64;
1119        let b = 1.0_f64 + 1e-15;
1120        assert_ne!(a, b);
1121        assert_approx(eval("DELTA(1, 1.000000000000001)"), 0.0, 0.01);
1122    }
1123
1124    #[test]
1125    fn gestep_above() {
1126        assert_approx(eval("GESTEP(5,4)"), 1.0, 0.01);
1127    }
1128
1129    #[test]
1130    fn gestep_below() {
1131        assert_approx(eval("GESTEP(3,4)"), 0.0, 0.01);
1132    }
1133
1134    #[test]
1135    fn gestep_equal() {
1136        assert_approx(eval("GESTEP(4,4)"), 1.0, 0.01);
1137    }
1138
1139    #[test]
1140    fn erf_basic() {
1141        assert_approx(eval("ERF(1)"), 0.8427, 0.001);
1142    }
1143
1144    #[test]
1145    fn erf_range() {
1146        assert_approx(eval("ERF(0,1)"), 0.8427, 0.001);
1147    }
1148
1149    #[test]
1150    fn erfc_basic() {
1151        assert_approx(eval("ERFC(1)"), 0.1573, 0.001);
1152    }
1153
1154    #[test]
1155    fn complex_basic() {
1156        assert_eq!(eval("COMPLEX(3,4)"), CellValue::String("3+4i".to_string()));
1157    }
1158
1159    #[test]
1160    fn complex_real_only() {
1161        assert_eq!(eval("COMPLEX(3,0)"), CellValue::String("3".to_string()));
1162    }
1163
1164    #[test]
1165    fn complex_imag_only() {
1166        assert_eq!(eval("COMPLEX(0,4)"), CellValue::String("4i".to_string()));
1167    }
1168
1169    #[test]
1170    fn complex_negative_imag() {
1171        assert_eq!(eval("COMPLEX(3,-4)"), CellValue::String("3-4i".to_string()));
1172    }
1173
1174    #[test]
1175    fn imreal_basic() {
1176        assert_approx(eval("IMREAL(\"3+4i\")"), 3.0, 0.01);
1177    }
1178
1179    #[test]
1180    fn imaginary_basic() {
1181        assert_approx(eval("IMAGINARY(\"3+4i\")"), 4.0, 0.01);
1182    }
1183
1184    #[test]
1185    fn imabs_basic() {
1186        assert_approx(eval("IMABS(\"3+4i\")"), 5.0, 0.01);
1187    }
1188
1189    #[test]
1190    fn imargument_basic() {
1191        assert_approx(eval("IMARGUMENT(\"3+4i\")"), (4.0_f64).atan2(3.0), 0.001);
1192    }
1193
1194    #[test]
1195    fn imconjugate_basic() {
1196        assert_eq!(
1197            eval("IMCONJUGATE(\"3+4i\")"),
1198            CellValue::String("3-4i".to_string())
1199        );
1200    }
1201
1202    #[test]
1203    fn imsum_basic() {
1204        assert_eq!(
1205            eval("IMSUM(\"3+4i\",\"1-2i\")"),
1206            CellValue::String("4+2i".to_string())
1207        );
1208    }
1209
1210    #[test]
1211    fn imsub_basic() {
1212        assert_eq!(
1213            eval("IMSUB(\"3+4i\",\"1+2i\")"),
1214            CellValue::String("2+2i".to_string())
1215        );
1216    }
1217
1218    #[test]
1219    fn improduct_basic() {
1220        assert_eq!(
1221            eval("IMPRODUCT(\"1+2i\",\"3+4i\")"),
1222            CellValue::String("-5+10i".to_string())
1223        );
1224    }
1225
1226    #[test]
1227    fn imdiv_basic() {
1228        let result = eval("IMDIV(\"2+4i\",\"1+1i\")");
1229        assert_eq!(result, CellValue::String("3+i".to_string()));
1230    }
1231
1232    #[test]
1233    fn impower_basic() {
1234        let result = eval("IMPOWER(\"2+3i\",2)");
1235        if let CellValue::String(s) = &result {
1236            let parsed = super::parse_complex(s).unwrap();
1237            assert!((parsed.0 - (-5.0)).abs() < 0.01);
1238            assert!((parsed.1 - 12.0).abs() < 0.01);
1239        } else {
1240            panic!("expected string, got {result:?}");
1241        }
1242    }
1243
1244    #[test]
1245    fn imsqrt_basic() {
1246        let result = eval("IMSQRT(\"4\")");
1247        if let CellValue::String(s) = &result {
1248            let parsed = super::parse_complex(s).unwrap();
1249            assert!((parsed.0 - 2.0).abs() < 0.01);
1250            assert!(parsed.1.abs() < 0.01);
1251        } else {
1252            panic!("expected string, got {result:?}");
1253        }
1254    }
1255
1256    #[test]
1257    fn convert_length() {
1258        assert_approx(eval("CONVERT(1,\"in\",\"cm\")"), 2.54, 0.001);
1259    }
1260
1261    #[test]
1262    fn convert_weight() {
1263        assert_approx(eval("CONVERT(1,\"lbm\",\"kg\")"), 0.453592, 0.001);
1264    }
1265
1266    #[test]
1267    fn convert_temperature() {
1268        assert_approx(eval("CONVERT(100,\"C\",\"F\")"), 212.0, 0.1);
1269    }
1270
1271    #[test]
1272    fn convert_temperature_k() {
1273        assert_approx(eval("CONVERT(0,\"C\",\"K\")"), 273.15, 0.01);
1274    }
1275
1276    #[test]
1277    fn convert_incompatible() {
1278        assert_eq!(
1279            eval("CONVERT(1,\"in\",\"kg\")"),
1280            CellValue::Error("#N/A".to_string())
1281        );
1282    }
1283
1284    #[test]
1285    fn besselj_basic() {
1286        assert_approx(eval("BESSELJ(1.9,2)"), 0.3295, 0.01);
1287    }
1288
1289    #[test]
1290    fn besseli_basic() {
1291        assert_approx(eval("BESSELI(1.5,1)"), 0.9817, 0.01);
1292    }
1293
1294    #[test]
1295    fn besselj_order_zero() {
1296        // BESSELJ(0, 0) = 1.0
1297        assert_approx(eval("BESSELJ(0,0)"), 1.0, 0.01);
1298    }
1299
1300    #[test]
1301    fn besselk_zero_x() {
1302        assert_eq!(eval("BESSELK(0,1)"), CellValue::Error("#NUM!".to_string()));
1303    }
1304
1305    #[test]
1306    fn besselk_integer_order() {
1307        // BESSELK(1.5, 1) should be finite (~0.2774)
1308        let result = eval("BESSELK(1.5,1)");
1309        if let CellValue::Number(v) = result {
1310            assert!(v.is_finite(), "BESSELK(1.5,1) should be finite, got {v}");
1311            assert!((v - 0.2774).abs() < 0.05);
1312        } else {
1313            panic!("expected number, got {result:?}");
1314        }
1315    }
1316
1317    #[test]
1318    fn bessely_zero_x() {
1319        assert_eq!(eval("BESSELY(0,1)"), CellValue::Error("#NUM!".to_string()));
1320    }
1321
1322    #[test]
1323    fn bessely_integer_order() {
1324        // BESSELY(2.5, 1) should be finite (~0.1459)
1325        let result = eval("BESSELY(2.5,1)");
1326        if let CellValue::Number(v) = result {
1327            assert!(v.is_finite(), "BESSELY(2.5,1) should be finite, got {v}");
1328            assert!((v - 0.1459).abs() < 0.05);
1329        } else {
1330            panic!("expected number, got {result:?}");
1331        }
1332    }
1333
1334    #[test]
1335    fn bessely_order_zero() {
1336        // BESSELY(1, 0) should be finite (~0.0883)
1337        let result = eval("BESSELY(1,0)");
1338        if let CellValue::Number(v) = result {
1339            assert!(v.is_finite(), "BESSELY(1,0) should be finite, got {v}");
1340            assert!((v - 0.0883).abs() < 0.05);
1341        } else {
1342            panic!("expected number, got {result:?}");
1343        }
1344    }
1345}