Skip to main content

truecalc_core/eval/functions/engineering/complex/
mod.rs

1use crate::eval::coercion::{to_number, to_string_val};
2use crate::eval::functions::check_arity;
3use crate::types::{ErrorKind, Value};
4
5// ── Internal complex type ─────────────────────────────────────────────────────
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub(super) struct Complex {
9    pub re: f64,
10    pub im: f64,
11}
12
13impl Complex {
14    fn new(re: f64, im: f64) -> Self {
15        Self { re, im }
16    }
17
18    fn abs(self) -> f64 {
19        (self.re * self.re + self.im * self.im).sqrt()
20    }
21
22    fn arg(self) -> f64 {
23        self.im.atan2(self.re)
24    }
25
26    fn mul(self, rhs: Self) -> Self {
27        Self {
28            re: self.re * rhs.re - self.im * rhs.im,
29            im: self.re * rhs.im + self.im * rhs.re,
30        }
31    }
32
33    fn pow(self, n: Self) -> Option<Self> {
34        // c^n = exp(n * ln(c))
35        let r = self.abs();
36        if r == 0.0 {
37            // 0^0 = 1 by convention; 0^n = 0 for n != 0
38            if n.re == 0.0 && n.im == 0.0 {
39                return Some(Complex::new(1.0, 0.0));
40            }
41            if n.re > 0.0 {
42                return Some(Complex::new(0.0, 0.0));
43            }
44            return None; // 0^negative
45        }
46        let theta = self.arg();
47        let ln_r = r.ln();
48        // ln(c) = ln_r + i*theta
49        // n * ln(c) = (n.re*ln_r - n.im*theta) + i*(n.im*ln_r + n.re*theta)
50        let exp_re = n.re * ln_r - n.im * theta;
51        let exp_im = n.im * ln_r + n.re * theta;
52        let scale = exp_re.exp();
53        Some(Complex::new(scale * exp_im.cos(), scale * exp_im.sin()))
54    }
55
56    fn sqrt(self) -> Self {
57        // Principal square root
58        let r = self.abs();
59        let sqrt_r = r.sqrt();
60        let theta = self.arg();
61        Complex::new(sqrt_r * (theta / 2.0).cos(), sqrt_r * (theta / 2.0).sin())
62    }
63
64    fn ln(self) -> Option<Self> {
65        let r = self.abs();
66        if r == 0.0 {
67            return None;
68        }
69        Some(Complex::new(r.ln(), self.arg()))
70    }
71}
72
73// ── Complex string parsing / formatting ──────────────────────────────────────
74
75/// Parse a complex number string like "3+4i", "3-4i", "i", "-i", "5", "2j", etc.
76/// Returns None on parse failure.
77pub(super) fn parse_complex(s: &str) -> Option<Complex> {
78    let s = s.trim();
79    if s.is_empty() {
80        return None;
81    }
82
83    // Detect suffix ('i' or 'j')
84    let suffix = if s.ends_with('i') || s.ends_with('j') {
85        Some(s.chars().last().unwrap())
86    } else {
87        None
88    };
89
90    if suffix.is_none() {
91        // Pure real number
92        let re = s.parse::<f64>().ok()?;
93        return Some(Complex::new(re, 0.0));
94    }
95
96    // Strip suffix
97    let s = &s[..s.len() - 1];
98
99    // Pure imaginary: "i", "-i", "+i"
100    if s.is_empty() || s == "+" {
101        return Some(Complex::new(0.0, 1.0));
102    }
103    if s == "-" {
104        return Some(Complex::new(0.0, -1.0));
105    }
106
107    // Try parsing the whole thing as real (shouldn't happen but cover edge case)
108    if !s.contains('+') && !s.contains('-') || s.starts_with('-') && s[1..].find(['+', '-']).is_none() {
109        // E.g. "4i" or "-4i"
110        let im = s.parse::<f64>().ok()?;
111        return Some(Complex::new(0.0, im));
112    }
113
114    // Find the split point between real and imaginary parts.
115    // We look for the last '+' or '-' that isn't at position 0 (sign of real).
116    let bytes = s.as_bytes();
117    let mut split = None;
118    let start = if bytes[0] == b'-' || bytes[0] == b'+' { 1 } else { 0 };
119    for i in (start + 1..bytes.len()).rev() {
120        if bytes[i] == b'+' || bytes[i] == b'-' {
121            split = Some(i);
122            break;
123        }
124    }
125
126    if let Some(idx) = split {
127        let re_str = &s[..idx];
128        let im_str = &s[idx..];
129
130        let re = if re_str.is_empty() { 0.0 } else { re_str.parse::<f64>().ok()? };
131        let im = if im_str == "+" || im_str.is_empty() {
132            1.0
133        } else if im_str == "-" {
134            -1.0
135        } else {
136            im_str.parse::<f64>().ok()?
137        };
138        Some(Complex::new(re, im))
139    } else {
140        // No split found; it's pure imaginary like "4i"
141        let im = s.parse::<f64>().ok()?;
142        Some(Complex::new(0.0, im))
143    }
144}
145
146/// Format a complex number back to a string using the given suffix ('i' or 'j').
147/// Always returns `Value::Text` — even for purely real results — to match
148/// the Google Sheets contract that all IM* functions return a text string.
149pub(super) fn format_complex(c: Complex, suffix: char) -> Value {
150    let re = c.re;
151    let im = c.im;
152
153    if im == 0.0 {
154        return Value::Text(format_num(re));
155    }
156
157    let re_str = if re == 0.0 {
158        String::new()
159    } else {
160        format_num(re)
161    };
162
163    let im_str = if im == 1.0 {
164        suffix.to_string()
165    } else if im == -1.0 {
166        format!("-{}", suffix)
167    } else {
168        format!("{}{}", format_num(im), suffix)
169    };
170
171    let result = if re == 0.0 {
172        im_str
173    } else if im > 0.0 {
174        format!("{}+{}", re_str, im_str)
175    } else {
176        format!("{}{}", re_str, im_str)
177    };
178
179    Value::Text(result)
180}
181
182/// Format a float component using Google Sheets' 15-significant-figure rules:
183/// - Whole numbers are printed without a decimal point.
184/// - Very small (|x| < 1e-9) or very large (|x| >= 1e15) values use uppercase
185///   scientific notation with up to 15 sig figs (e.g. `6.12323399573677E-17`).
186/// - All other values use decimal notation with up to 15 sig figs, trailing
187///   zeros stripped.
188fn format_num(n: f64) -> String {
189    if n == 0.0 {
190        return "0".to_string();
191    }
192    if n.fract() == 0.0 && n.abs() < 1e15 {
193        return format!("{}", n as i64);
194    }
195
196    let abs = n.abs();
197
198    if !(1e-9..1e15).contains(&abs) {
199        // Scientific notation: 14 decimal places = 15 significant figures.
200        let s = format!("{:.14e}", n);
201        let (mantissa, exp_part) = s.split_once('e').unwrap();
202        let exp_num: i32 = exp_part.parse().unwrap();
203        let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
204        // GS uses uppercase E with a sign and at least 2 digits in the exponent.
205        format!("{}E{:+03}", mantissa, exp_num)
206    } else {
207        // Convert via 15-sig-fig scientific notation, then render as decimal.
208        let s = format!("{:.14e}", n);
209        let (mantissa, exp_part) = s.split_once('e').unwrap();
210        let exp_num: i32 = exp_part.parse().unwrap();
211        let mantissa_stripped = mantissa.trim_end_matches('0').trim_end_matches('.');
212        let sign = if n < 0.0 { "-" } else { "" };
213        let mantissa_abs = mantissa_stripped.trim_start_matches('-');
214        let digits: String = mantissa_abs.chars().filter(|c| *c != '.').collect();
215
216        if exp_num < 0 {
217            let leading_zeros = (-exp_num - 1) as usize;
218            format!("{}0.{}{}", sign, "0".repeat(leading_zeros), digits)
219        } else {
220            let int_part_len = (exp_num + 1) as usize;
221            if int_part_len >= digits.len() {
222                format!(
223                    "{}{}{}",
224                    sign,
225                    digits,
226                    "0".repeat(int_part_len - digits.len())
227                )
228            } else {
229                let (int_part, frac_part) = digits.split_at(int_part_len);
230                format!("{}{}.{}", sign, int_part, frac_part)
231            }
232        }
233    }
234}
235
236/// Parse a Value as a complex number. Accepts Text or Number.
237fn value_to_complex(v: Value) -> Result<Complex, Value> {
238    match v {
239        Value::Number(n) | Value::Date(n) => Ok(Complex::new(n, 0.0)),
240        Value::Text(s) => {
241            parse_complex(&s).ok_or(Value::Error(ErrorKind::Value))
242        }
243        Value::Error(_) => Err(v),
244        _ => {
245            match to_number(v) {
246                Ok(n) => Ok(Complex::new(n, 0.0)),
247                Err(e) => Err(e),
248            }
249        }
250    }
251}
252
253// ── COMPLEX ───────────────────────────────────────────────────────────────────
254
255/// `COMPLEX(real, imaginary, [suffix])` — create a complex number string.
256pub fn complex_fn(args: &[Value]) -> Value {
257    if let Some(err) = check_arity(args, 2, 3) {
258        return err;
259    }
260    let re = match to_number(args[0].clone()) {
261        Err(e) => return e,
262        Ok(v) => v,
263    };
264    let im = match to_number(args[1].clone()) {
265        Err(e) => return e,
266        Ok(v) => v,
267    };
268    let suffix = if args.len() == 3 {
269        match to_string_val(args[2].clone()) {
270            Err(e) => return e,
271            Ok(s) => {
272                if s == "i" || s == "j" {
273                    s.chars().next().unwrap()
274                } else {
275                    return Value::Error(ErrorKind::Value);
276                }
277            }
278        }
279    } else {
280        'i'
281    };
282    format_complex(Complex::new(re, im), suffix)
283}
284
285// ── IMREAL / IMAGINARY ────────────────────────────────────────────────────────
286
287/// `IMREAL(complex)` — return real part of complex number.
288pub fn imreal_fn(args: &[Value]) -> Value {
289    if let Some(err) = check_arity(args, 1, 1) {
290        return err;
291    }
292    match value_to_complex(args[0].clone()) {
293        Err(e) => e,
294        Ok(c) => Value::Number(c.re),
295    }
296}
297
298/// `IMAGINARY(complex)` — return imaginary part of complex number.
299pub fn imaginary_fn(args: &[Value]) -> Value {
300    if let Some(err) = check_arity(args, 1, 1) {
301        return err;
302    }
303    match value_to_complex(args[0].clone()) {
304        Err(e) => e,
305        Ok(c) => Value::Number(c.im),
306    }
307}
308
309// ── IMABS ─────────────────────────────────────────────────────────────────────
310
311/// `IMABS(complex)` — return absolute value (modulus) of complex number.
312pub fn imabs_fn(args: &[Value]) -> Value {
313    if let Some(err) = check_arity(args, 1, 1) {
314        return err;
315    }
316    match value_to_complex(args[0].clone()) {
317        Err(e) => e,
318        Ok(c) => Value::Number(c.abs()),
319    }
320}
321
322// ── IMPRODUCT ─────────────────────────────────────────────────────────────────
323
324/// `IMPRODUCT(complex1, ...)` — product of complex numbers.
325pub fn improduct_fn(args: &[Value]) -> Value {
326    if let Some(err) = check_arity(args, 1, usize::MAX) {
327        return err;
328    }
329    let mut result = Complex::new(1.0, 0.0);
330    for arg in args {
331        match value_to_complex(arg.clone()) {
332            Err(e) => return e,
333            Ok(c) => result = result.mul(c),
334        }
335    }
336    format_complex(result, 'i')
337}
338
339// ── IMSUB ────────────────────────────────────────────────────────────────────
340
341/// `IMSUB(complex1, complex2)` — subtract complex numbers.
342pub fn imsub_fn(args: &[Value]) -> Value {
343    if let Some(err) = check_arity(args, 2, 2) {
344        return err;
345    }
346    let a = match value_to_complex(args[0].clone()) {
347        Err(e) => return e,
348        Ok(c) => c,
349    };
350    let b = match value_to_complex(args[1].clone()) {
351        Err(e) => return e,
352        Ok(c) => c,
353    };
354    format_complex(Complex::new(a.re - b.re, a.im - b.im), 'i')
355}
356
357// ── IMSUM ────────────────────────────────────────────────────────────────────
358
359/// `IMSUM(complex1, ...)` — sum of complex numbers.
360pub fn imsum_fn(args: &[Value]) -> Value {
361    if let Some(err) = check_arity(args, 1, usize::MAX) {
362        return err;
363    }
364    let mut re = 0.0f64;
365    let mut im = 0.0f64;
366    for arg in args {
367        match value_to_complex(arg.clone()) {
368            Err(e) => return e,
369            Ok(c) => {
370                re += c.re;
371                im += c.im;
372            }
373        }
374    }
375    format_complex(Complex::new(re, im), 'i')
376}
377
378// ── IMDIV ────────────────────────────────────────────────────────────────────
379
380/// `IMDIV(complex1, complex2)` — divide complex numbers.
381pub fn imdiv_fn(args: &[Value]) -> Value {
382    if let Some(err) = check_arity(args, 2, 2) {
383        return err;
384    }
385    let a = match value_to_complex(args[0].clone()) {
386        Err(e) => return e,
387        Ok(c) => c,
388    };
389    let b = match value_to_complex(args[1].clone()) {
390        Err(e) => return e,
391        Ok(c) => c,
392    };
393    let denom = b.re * b.re + b.im * b.im;
394    if denom == 0.0 {
395        return Value::Error(ErrorKind::DivByZero);
396    }
397    let re = (a.re * b.re + a.im * b.im) / denom;
398    let im = (a.im * b.re - a.re * b.im) / denom;
399    format_complex(Complex::new(re, im), 'i')
400}
401
402// ── IMCONJUGATE ───────────────────────────────────────────────────────────────
403
404/// `IMCONJUGATE(complex)` — complex conjugate.
405pub fn imconjugate_fn(args: &[Value]) -> Value {
406    if let Some(err) = check_arity(args, 1, 1) {
407        return err;
408    }
409    match value_to_complex(args[0].clone()) {
410        Err(e) => e,
411        Ok(c) => format_complex(Complex::new(c.re, -c.im), 'i'),
412    }
413}
414
415// ── IMARGUMENT ────────────────────────────────────────────────────────────────
416
417/// `IMARGUMENT(complex)` — argument (angle) of complex number.
418pub fn imargument_fn(args: &[Value]) -> Value {
419    if let Some(err) = check_arity(args, 1, 1) {
420        return err;
421    }
422    match value_to_complex(args[0].clone()) {
423        Err(e) => e,
424        Ok(c) => {
425            if c.re == 0.0 && c.im == 0.0 {
426                Value::Error(ErrorKind::DivByZero)
427            } else {
428                Value::Number(c.arg())
429            }
430        }
431    }
432}
433
434// ── IMLN ─────────────────────────────────────────────────────────────────────
435
436/// `IMLN(complex)` — natural log of complex number.
437pub fn imln_fn(args: &[Value]) -> Value {
438    if let Some(err) = check_arity(args, 1, 1) {
439        return err;
440    }
441    match value_to_complex(args[0].clone()) {
442        Err(e) => e,
443        Ok(c) => match c.ln() {
444            None => Value::Error(ErrorKind::DivByZero),
445            Some(result) => format_complex(result, 'i'),
446        },
447    }
448}
449
450// ── IMLOG10 / IMLOG2 / IMLOG ─────────────────────────────────────────────────
451
452/// `IMLOG10(complex)` — base-10 log of complex number.
453pub fn imlog10_fn(args: &[Value]) -> Value {
454    if let Some(err) = check_arity(args, 1, 1) {
455        return err;
456    }
457    match value_to_complex(args[0].clone()) {
458        Err(e) => e,
459        Ok(c) => match c.ln() {
460            None => Value::Error(ErrorKind::DivByZero),
461            Some(result) => {
462                let ln10 = 10.0f64.ln();
463                format_complex(Complex::new(result.re / ln10, result.im / ln10), 'i')
464            }
465        },
466    }
467}
468
469/// `IMLOG2(complex)` — base-2 log of complex number.
470pub fn imlog2_fn(args: &[Value]) -> Value {
471    if let Some(err) = check_arity(args, 1, 1) {
472        return err;
473    }
474    match value_to_complex(args[0].clone()) {
475        Err(e) => e,
476        Ok(c) => match c.ln() {
477            None => Value::Error(ErrorKind::DivByZero),
478            Some(result) => {
479                let ln2 = 2.0f64.ln();
480                format_complex(Complex::new(result.re / ln2, result.im / ln2), 'i')
481            }
482        },
483    }
484}
485
486/// `IMLOG(complex, base)` — general log of complex number.
487pub fn imlog_fn(args: &[Value]) -> Value {
488    if let Some(err) = check_arity(args, 2, 2) {
489        return err;
490    }
491    let c = match value_to_complex(args[0].clone()) {
492        Err(e) => return e,
493        Ok(v) => v,
494    };
495    let base = match to_number(args[1].clone()) {
496        Err(e) => return e,
497        Ok(v) => v,
498    };
499    if base <= 0.0 || base == 1.0 {
500        return Value::Error(ErrorKind::Num);
501    }
502    match c.ln() {
503        None => Value::Error(ErrorKind::DivByZero),
504        Some(result) => {
505            let ln_base = base.ln();
506            format_complex(Complex::new(result.re / ln_base, result.im / ln_base), 'i')
507        }
508    }
509}
510
511// ── IMEXP ────────────────────────────────────────────────────────────────────
512
513/// `IMEXP(complex)` — e raised to a complex power.
514pub fn imexp_fn(args: &[Value]) -> Value {
515    if let Some(err) = check_arity(args, 1, 1) {
516        return err;
517    }
518    match value_to_complex(args[0].clone()) {
519        Err(e) => e,
520        Ok(c) => {
521            let scale = c.re.exp();
522            format_complex(Complex::new(scale * c.im.cos(), scale * c.im.sin()), 'i')
523        }
524    }
525}
526
527// ── IMPOWER ──────────────────────────────────────────────────────────────────
528
529/// `IMPOWER(complex, number)` — complex number raised to a power.
530pub fn impower_fn(args: &[Value]) -> Value {
531    if let Some(err) = check_arity(args, 2, 2) {
532        return err;
533    }
534    let base = match value_to_complex(args[0].clone()) {
535        Err(e) => return e,
536        Ok(c) => c,
537    };
538    let exp = match value_to_complex(args[1].clone()) {
539        Err(e) => return e,
540        Ok(c) => c,
541    };
542    match base.pow(exp) {
543        None => Value::Error(ErrorKind::Num),
544        Some(result) => format_complex(result, 'i'),
545    }
546}
547
548// ── IMSQRT ───────────────────────────────────────────────────────────────────
549
550/// `IMSQRT(complex)` — principal square root of complex number.
551pub fn imsqrt_fn(args: &[Value]) -> Value {
552    if let Some(err) = check_arity(args, 1, 1) {
553        return err;
554    }
555    match value_to_complex(args[0].clone()) {
556        Err(e) => e,
557        Ok(c) => format_complex(c.sqrt(), 'i'),
558    }
559}
560
561// ── Trig functions ────────────────────────────────────────────────────────────
562
563/// `IMSIN(complex)` — sine of complex number.
564pub fn imsin_fn(args: &[Value]) -> Value {
565    if let Some(err) = check_arity(args, 1, 1) {
566        return err;
567    }
568    match value_to_complex(args[0].clone()) {
569        Err(e) => e,
570        Ok(c) => {
571            let re = c.re.sin() * c.im.cosh();
572            let im = c.re.cos() * c.im.sinh();
573            format_complex(Complex::new(re, im), 'i')
574        }
575    }
576}
577
578/// `IMCOS(complex)` — cosine of complex number.
579pub fn imcos_fn(args: &[Value]) -> Value {
580    if let Some(err) = check_arity(args, 1, 1) {
581        return err;
582    }
583    match value_to_complex(args[0].clone()) {
584        Err(e) => e,
585        Ok(c) => {
586            let re = c.re.cos() * c.im.cosh();
587            let im = -(c.re.sin() * c.im.sinh());
588            format_complex(Complex::new(re, im), 'i')
589        }
590    }
591}
592
593/// `IMTAN(complex)` — tangent of complex number.
594pub fn imtan_fn(args: &[Value]) -> Value {
595    if let Some(err) = check_arity(args, 1, 1) {
596        return err;
597    }
598    match value_to_complex(args[0].clone()) {
599        Err(e) => e,
600        Ok(c) => {
601            let sin_re = c.re.sin() * c.im.cosh();
602            let sin_im = c.re.cos() * c.im.sinh();
603            let cos_re = c.re.cos() * c.im.cosh();
604            let cos_im = -(c.re.sin() * c.im.sinh());
605            let denom = cos_re * cos_re + cos_im * cos_im;
606            if denom == 0.0 {
607                return Value::Error(ErrorKind::DivByZero);
608            }
609            let re = (sin_re * cos_re + sin_im * cos_im) / denom;
610            let im = (sin_im * cos_re - sin_re * cos_im) / denom;
611            format_complex(Complex::new(re, im), 'i')
612        }
613    }
614}
615
616/// `IMCOT(complex)` — cotangent of complex number.
617pub fn imcot_fn(args: &[Value]) -> Value {
618    if let Some(err) = check_arity(args, 1, 1) {
619        return err;
620    }
621    match value_to_complex(args[0].clone()) {
622        Err(e) => e,
623        Ok(c) => {
624            let sin_re = c.re.sin() * c.im.cosh();
625            let sin_im = c.re.cos() * c.im.sinh();
626            let cos_re = c.re.cos() * c.im.cosh();
627            let cos_im = -(c.re.sin() * c.im.sinh());
628            let denom = sin_re * sin_re + sin_im * sin_im;
629            if denom == 0.0 {
630                return Value::Error(ErrorKind::DivByZero);
631            }
632            let re = (cos_re * sin_re + cos_im * sin_im) / denom;
633            let im = (cos_im * sin_re - cos_re * sin_im) / denom;
634            format_complex(Complex::new(re, im), 'i')
635        }
636    }
637}
638
639/// `IMCSC(complex)` — cosecant of complex number.
640pub fn imcsc_fn(args: &[Value]) -> Value {
641    if let Some(err) = check_arity(args, 1, 1) {
642        return err;
643    }
644    match value_to_complex(args[0].clone()) {
645        Err(e) => e,
646        Ok(c) => {
647            let sin_re = c.re.sin() * c.im.cosh();
648            let sin_im = c.re.cos() * c.im.sinh();
649            let denom = sin_re * sin_re + sin_im * sin_im;
650            if denom == 0.0 {
651                return Value::Error(ErrorKind::Num);
652            }
653            let re = sin_re / denom;
654            let im = -sin_im / denom;
655            format_complex(Complex::new(re, im), 'i')
656        }
657    }
658}
659
660/// `IMSEC(complex)` — secant of complex number.
661pub fn imsec_fn(args: &[Value]) -> Value {
662    if let Some(err) = check_arity(args, 1, 1) {
663        return err;
664    }
665    match value_to_complex(args[0].clone()) {
666        Err(e) => e,
667        Ok(c) => {
668            let cos_re = c.re.cos() * c.im.cosh();
669            let cos_im = -(c.re.sin() * c.im.sinh());
670            let denom = cos_re * cos_re + cos_im * cos_im;
671            if denom == 0.0 {
672                return Value::Error(ErrorKind::DivByZero);
673            }
674            let re = cos_re / denom;
675            let im = -cos_im / denom;
676            format_complex(Complex::new(re, im), 'i')
677        }
678    }
679}
680
681// ── Hyperbolic trig ────────────────────────────────────────────────────────────
682
683/// `IMSINH(complex)` — hyperbolic sine of complex number.
684pub fn imsinh_fn(args: &[Value]) -> Value {
685    if let Some(err) = check_arity(args, 1, 1) {
686        return err;
687    }
688    match value_to_complex(args[0].clone()) {
689        Err(e) => e,
690        Ok(c) => {
691            let re = c.re.sinh() * c.im.cos();
692            let im = c.re.cosh() * c.im.sin();
693            format_complex(Complex::new(re, im), 'i')
694        }
695    }
696}
697
698/// `IMCOSH(complex)` — hyperbolic cosine of complex number.
699pub fn imcosh_fn(args: &[Value]) -> Value {
700    if let Some(err) = check_arity(args, 1, 1) {
701        return err;
702    }
703    match value_to_complex(args[0].clone()) {
704        Err(e) => e,
705        Ok(c) => {
706            let re = c.re.cosh() * c.im.cos();
707            let im = c.re.sinh() * c.im.sin();
708            format_complex(Complex::new(re, im), 'i')
709        }
710    }
711}
712
713/// `IMTANH(complex)` — hyperbolic tangent of complex number.
714pub fn imtanh_fn(args: &[Value]) -> Value {
715    if let Some(err) = check_arity(args, 1, 1) {
716        return err;
717    }
718    match value_to_complex(args[0].clone()) {
719        Err(e) => e,
720        Ok(c) => {
721            let sinh_re = c.re.sinh() * c.im.cos();
722            let sinh_im = c.re.cosh() * c.im.sin();
723            let cosh_re = c.re.cosh() * c.im.cos();
724            let cosh_im = c.re.sinh() * c.im.sin();
725            let denom = cosh_re * cosh_re + cosh_im * cosh_im;
726            if denom == 0.0 {
727                return Value::Error(ErrorKind::DivByZero);
728            }
729            let re = (sinh_re * cosh_re + sinh_im * cosh_im) / denom;
730            let im = (sinh_im * cosh_re - sinh_re * cosh_im) / denom;
731            if im == 0.0 {
732                return Value::Text(format!("{}", re));
733            }
734            format_complex(Complex::new(re, im), 'i')
735        }
736    }
737}
738
739/// `IMCOTH(complex)` — hyperbolic cotangent of complex number.
740pub fn imcoth_fn(args: &[Value]) -> Value {
741    if let Some(err) = check_arity(args, 1, 1) {
742        return err;
743    }
744    match value_to_complex(args[0].clone()) {
745        Err(e) => e,
746        Ok(c) => {
747            let sinh_re = c.re.sinh() * c.im.cos();
748            let sinh_im = c.re.cosh() * c.im.sin();
749            let cosh_re = c.re.cosh() * c.im.cos();
750            let cosh_im = c.re.sinh() * c.im.sin();
751            let denom = sinh_re * sinh_re + sinh_im * sinh_im;
752            if denom == 0.0 {
753                return Value::Error(ErrorKind::DivByZero);
754            }
755            let re = (cosh_re * sinh_re + cosh_im * sinh_im) / denom;
756            let im = (cosh_im * sinh_re - cosh_re * sinh_im) / denom;
757            if im == 0.0 {
758                return Value::Text(format!("{}", re));
759            }
760            format_complex(Complex::new(re, im), 'i')
761        }
762    }
763}
764
765/// `IMCSCH(complex)` — hyperbolic cosecant of complex number.
766pub fn imcsch_fn(args: &[Value]) -> Value {
767    if let Some(err) = check_arity(args, 1, 1) {
768        return err;
769    }
770    match value_to_complex(args[0].clone()) {
771        Err(e) => e,
772        Ok(c) => {
773            let sinh_re = c.re.sinh() * c.im.cos();
774            let sinh_im = c.re.cosh() * c.im.sin();
775            let denom = sinh_re * sinh_re + sinh_im * sinh_im;
776            if denom == 0.0 {
777                return Value::Error(ErrorKind::DivByZero);
778            }
779            let re = sinh_re / denom;
780            let im = -sinh_im / denom;
781            format_complex(Complex::new(re, im), 'i')
782        }
783    }
784}
785
786/// `IMSECH(complex)` — hyperbolic secant of complex number.
787pub fn imsech_fn(args: &[Value]) -> Value {
788    if let Some(err) = check_arity(args, 1, 1) {
789        return err;
790    }
791    match value_to_complex(args[0].clone()) {
792        Err(e) => e,
793        Ok(c) => {
794            let cosh_re = c.re.cosh() * c.im.cos();
795            let cosh_im = c.re.sinh() * c.im.sin();
796            let denom = cosh_re * cosh_re + cosh_im * cosh_im;
797            if denom == 0.0 {
798                return Value::Error(ErrorKind::DivByZero);
799            }
800            let re = cosh_re / denom;
801            let im = -cosh_im / denom;
802            format_complex(Complex::new(re, im), 'i')
803        }
804    }
805}
806
807#[cfg(test)]
808mod tests;