Skip to main content

ganit_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').
147pub(super) fn format_complex(c: Complex, suffix: char) -> Value {
148    let re = c.re;
149    let im = c.im;
150
151    // Clean up near-zero values
152    let re = if re.abs() < 1e-10 { 0.0 } else { re };
153    let im = if im.abs() < 1e-10 { 0.0 } else { im };
154
155    if im == 0.0 {
156        return Value::Number(re);
157    }
158
159    let re_str = if re == 0.0 {
160        String::new()
161    } else {
162        format_num(re)
163    };
164
165    let im_str = if im == 1.0 {
166        suffix.to_string()
167    } else if im == -1.0 {
168        format!("-{}", suffix)
169    } else {
170        format!("{}{}", format_num(im), suffix)
171    };
172
173    let result = if re == 0.0 {
174        im_str
175    } else if im > 0.0 || im == 1.0 {
176        format!("{}+{}", re_str, im_str)
177    } else {
178        format!("{}{}", re_str, im_str)
179    };
180
181    Value::Text(result)
182}
183
184fn format_num(n: f64) -> String {
185    // Use integer if it's a whole number
186    if n.fract() == 0.0 && n.abs() < 1e15 {
187        format!("{}", n as i64)
188    } else {
189        format!("{}", n)
190    }
191}
192
193/// Parse a Value as a complex number. Accepts Text or Number.
194fn value_to_complex(v: Value) -> Result<Complex, Value> {
195    match v {
196        Value::Number(n) | Value::Date(n) => Ok(Complex::new(n, 0.0)),
197        Value::Text(s) => {
198            parse_complex(&s).ok_or(Value::Error(ErrorKind::Value))
199        }
200        Value::Error(_) => Err(v),
201        _ => {
202            match to_number(v) {
203                Ok(n) => Ok(Complex::new(n, 0.0)),
204                Err(e) => Err(e),
205            }
206        }
207    }
208}
209
210// ── COMPLEX ───────────────────────────────────────────────────────────────────
211
212/// `COMPLEX(real, imaginary, [suffix])` — create a complex number string.
213pub fn complex_fn(args: &[Value]) -> Value {
214    if let Some(err) = check_arity(args, 2, 3) {
215        return err;
216    }
217    let re = match to_number(args[0].clone()) {
218        Err(e) => return e,
219        Ok(v) => v,
220    };
221    let im = match to_number(args[1].clone()) {
222        Err(e) => return e,
223        Ok(v) => v,
224    };
225    let suffix = if args.len() == 3 {
226        match to_string_val(args[2].clone()) {
227            Err(e) => return e,
228            Ok(s) => {
229                if s == "i" || s == "j" {
230                    s.chars().next().unwrap()
231                } else {
232                    return Value::Error(ErrorKind::Value);
233                }
234            }
235        }
236    } else {
237        'i'
238    };
239    format_complex(Complex::new(re, im), suffix)
240}
241
242// ── IMREAL / IMAGINARY ────────────────────────────────────────────────────────
243
244/// `IMREAL(complex)` — return real part of complex number.
245pub fn imreal_fn(args: &[Value]) -> Value {
246    if let Some(err) = check_arity(args, 1, 1) {
247        return err;
248    }
249    match value_to_complex(args[0].clone()) {
250        Err(e) => e,
251        Ok(c) => Value::Number(c.re),
252    }
253}
254
255/// `IMAGINARY(complex)` — return imaginary part of complex number.
256pub fn imaginary_fn(args: &[Value]) -> Value {
257    if let Some(err) = check_arity(args, 1, 1) {
258        return err;
259    }
260    match value_to_complex(args[0].clone()) {
261        Err(e) => e,
262        Ok(c) => Value::Number(c.im),
263    }
264}
265
266// ── IMABS ─────────────────────────────────────────────────────────────────────
267
268/// `IMABS(complex)` — return absolute value (modulus) of complex number.
269pub fn imabs_fn(args: &[Value]) -> Value {
270    if let Some(err) = check_arity(args, 1, 1) {
271        return err;
272    }
273    match value_to_complex(args[0].clone()) {
274        Err(e) => e,
275        Ok(c) => Value::Number(c.abs()),
276    }
277}
278
279// ── IMPRODUCT ─────────────────────────────────────────────────────────────────
280
281/// `IMPRODUCT(complex1, ...)` — product of complex numbers.
282pub fn improduct_fn(args: &[Value]) -> Value {
283    if let Some(err) = check_arity(args, 1, usize::MAX) {
284        return err;
285    }
286    let mut result = Complex::new(1.0, 0.0);
287    for arg in args {
288        match value_to_complex(arg.clone()) {
289            Err(e) => return e,
290            Ok(c) => result = result.mul(c),
291        }
292    }
293    format_complex(result, 'i')
294}
295
296// ── IMSUB ────────────────────────────────────────────────────────────────────
297
298/// `IMSUB(complex1, complex2)` — subtract complex numbers.
299pub fn imsub_fn(args: &[Value]) -> Value {
300    if let Some(err) = check_arity(args, 2, 2) {
301        return err;
302    }
303    let a = match value_to_complex(args[0].clone()) {
304        Err(e) => return e,
305        Ok(c) => c,
306    };
307    let b = match value_to_complex(args[1].clone()) {
308        Err(e) => return e,
309        Ok(c) => c,
310    };
311    format_complex(Complex::new(a.re - b.re, a.im - b.im), 'i')
312}
313
314// ── IMSUM ────────────────────────────────────────────────────────────────────
315
316/// `IMSUM(complex1, ...)` — sum of complex numbers.
317pub fn imsum_fn(args: &[Value]) -> Value {
318    if let Some(err) = check_arity(args, 1, usize::MAX) {
319        return err;
320    }
321    let mut re = 0.0f64;
322    let mut im = 0.0f64;
323    for arg in args {
324        match value_to_complex(arg.clone()) {
325            Err(e) => return e,
326            Ok(c) => {
327                re += c.re;
328                im += c.im;
329            }
330        }
331    }
332    format_complex(Complex::new(re, im), 'i')
333}
334
335// ── IMDIV ────────────────────────────────────────────────────────────────────
336
337/// `IMDIV(complex1, complex2)` — divide complex numbers.
338pub fn imdiv_fn(args: &[Value]) -> Value {
339    if let Some(err) = check_arity(args, 2, 2) {
340        return err;
341    }
342    let a = match value_to_complex(args[0].clone()) {
343        Err(e) => return e,
344        Ok(c) => c,
345    };
346    let b = match value_to_complex(args[1].clone()) {
347        Err(e) => return e,
348        Ok(c) => c,
349    };
350    let denom = b.re * b.re + b.im * b.im;
351    if denom == 0.0 {
352        return Value::Error(ErrorKind::DivByZero);
353    }
354    let re = (a.re * b.re + a.im * b.im) / denom;
355    let im = (a.im * b.re - a.re * b.im) / denom;
356    format_complex(Complex::new(re, im), 'i')
357}
358
359// ── IMCONJUGATE ───────────────────────────────────────────────────────────────
360
361/// `IMCONJUGATE(complex)` — complex conjugate.
362pub fn imconjugate_fn(args: &[Value]) -> Value {
363    if let Some(err) = check_arity(args, 1, 1) {
364        return err;
365    }
366    match value_to_complex(args[0].clone()) {
367        Err(e) => e,
368        Ok(c) => format_complex(Complex::new(c.re, -c.im), 'i'),
369    }
370}
371
372// ── IMARGUMENT ────────────────────────────────────────────────────────────────
373
374/// `IMARGUMENT(complex)` — argument (angle) of complex number.
375pub fn imargument_fn(args: &[Value]) -> Value {
376    if let Some(err) = check_arity(args, 1, 1) {
377        return err;
378    }
379    match value_to_complex(args[0].clone()) {
380        Err(e) => e,
381        Ok(c) => {
382            if c.re == 0.0 && c.im == 0.0 {
383                Value::Error(ErrorKind::DivByZero)
384            } else {
385                Value::Number(c.arg())
386            }
387        }
388    }
389}
390
391// ── IMLN ─────────────────────────────────────────────────────────────────────
392
393/// `IMLN(complex)` — natural log of complex number.
394pub fn imln_fn(args: &[Value]) -> Value {
395    if let Some(err) = check_arity(args, 1, 1) {
396        return err;
397    }
398    match value_to_complex(args[0].clone()) {
399        Err(e) => e,
400        Ok(c) => match c.ln() {
401            None => Value::Error(ErrorKind::DivByZero),
402            Some(result) => format_complex(result, 'i'),
403        },
404    }
405}
406
407// ── IMLOG10 / IMLOG2 / IMLOG ─────────────────────────────────────────────────
408
409/// `IMLOG10(complex)` — base-10 log of complex number.
410pub fn imlog10_fn(args: &[Value]) -> Value {
411    if let Some(err) = check_arity(args, 1, 1) {
412        return err;
413    }
414    match value_to_complex(args[0].clone()) {
415        Err(e) => e,
416        Ok(c) => match c.ln() {
417            None => Value::Error(ErrorKind::DivByZero),
418            Some(result) => {
419                let ln10 = 10.0f64.ln();
420                format_complex(Complex::new(result.re / ln10, result.im / ln10), 'i')
421            }
422        },
423    }
424}
425
426/// `IMLOG2(complex)` — base-2 log of complex number.
427pub fn imlog2_fn(args: &[Value]) -> Value {
428    if let Some(err) = check_arity(args, 1, 1) {
429        return err;
430    }
431    match value_to_complex(args[0].clone()) {
432        Err(e) => e,
433        Ok(c) => match c.ln() {
434            None => Value::Error(ErrorKind::DivByZero),
435            Some(result) => {
436                let ln2 = 2.0f64.ln();
437                format_complex(Complex::new(result.re / ln2, result.im / ln2), 'i')
438            }
439        },
440    }
441}
442
443/// `IMLOG(complex, base)` — general log of complex number.
444pub fn imlog_fn(args: &[Value]) -> Value {
445    if let Some(err) = check_arity(args, 2, 2) {
446        return err;
447    }
448    let c = match value_to_complex(args[0].clone()) {
449        Err(e) => return e,
450        Ok(v) => v,
451    };
452    let base = match to_number(args[1].clone()) {
453        Err(e) => return e,
454        Ok(v) => v,
455    };
456    if base <= 0.0 || base == 1.0 {
457        return Value::Error(ErrorKind::Num);
458    }
459    match c.ln() {
460        None => Value::Error(ErrorKind::DivByZero),
461        Some(result) => {
462            let ln_base = base.ln();
463            format_complex(Complex::new(result.re / ln_base, result.im / ln_base), 'i')
464        }
465    }
466}
467
468// ── IMEXP ────────────────────────────────────────────────────────────────────
469
470/// `IMEXP(complex)` — e raised to a complex power.
471pub fn imexp_fn(args: &[Value]) -> Value {
472    if let Some(err) = check_arity(args, 1, 1) {
473        return err;
474    }
475    match value_to_complex(args[0].clone()) {
476        Err(e) => e,
477        Ok(c) => {
478            let scale = c.re.exp();
479            format_complex(Complex::new(scale * c.im.cos(), scale * c.im.sin()), 'i')
480        }
481    }
482}
483
484// ── IMPOWER ──────────────────────────────────────────────────────────────────
485
486/// `IMPOWER(complex, number)` — complex number raised to a power.
487pub fn impower_fn(args: &[Value]) -> Value {
488    if let Some(err) = check_arity(args, 2, 2) {
489        return err;
490    }
491    let base = match value_to_complex(args[0].clone()) {
492        Err(e) => return e,
493        Ok(c) => c,
494    };
495    let exp = match value_to_complex(args[1].clone()) {
496        Err(e) => return e,
497        Ok(c) => c,
498    };
499    match base.pow(exp) {
500        None => Value::Error(ErrorKind::Num),
501        Some(result) => format_complex(result, 'i'),
502    }
503}
504
505// ── IMSQRT ───────────────────────────────────────────────────────────────────
506
507/// `IMSQRT(complex)` — principal square root of complex number.
508pub fn imsqrt_fn(args: &[Value]) -> Value {
509    if let Some(err) = check_arity(args, 1, 1) {
510        return err;
511    }
512    match value_to_complex(args[0].clone()) {
513        Err(e) => e,
514        Ok(c) => format_complex(c.sqrt(), 'i'),
515    }
516}
517
518// ── Trig functions ────────────────────────────────────────────────────────────
519
520/// `IMSIN(complex)` — sine of complex number.
521pub fn imsin_fn(args: &[Value]) -> Value {
522    if let Some(err) = check_arity(args, 1, 1) {
523        return err;
524    }
525    match value_to_complex(args[0].clone()) {
526        Err(e) => e,
527        Ok(c) => {
528            let re = c.re.sin() * c.im.cosh();
529            let im = c.re.cos() * c.im.sinh();
530            format_complex(Complex::new(re, im), 'i')
531        }
532    }
533}
534
535/// `IMCOS(complex)` — cosine of complex number.
536pub fn imcos_fn(args: &[Value]) -> Value {
537    if let Some(err) = check_arity(args, 1, 1) {
538        return err;
539    }
540    match value_to_complex(args[0].clone()) {
541        Err(e) => e,
542        Ok(c) => {
543            let re = c.re.cos() * c.im.cosh();
544            let im = -(c.re.sin() * c.im.sinh());
545            format_complex(Complex::new(re, im), 'i')
546        }
547    }
548}
549
550/// `IMTAN(complex)` — tangent of complex number.
551pub fn imtan_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) => {
558            let sin_re = c.re.sin() * c.im.cosh();
559            let sin_im = c.re.cos() * c.im.sinh();
560            let cos_re = c.re.cos() * c.im.cosh();
561            let cos_im = -(c.re.sin() * c.im.sinh());
562            let denom = cos_re * cos_re + cos_im * cos_im;
563            if denom == 0.0 {
564                return Value::Error(ErrorKind::DivByZero);
565            }
566            let re = (sin_re * cos_re + sin_im * cos_im) / denom;
567            let im = (sin_im * cos_re - sin_re * cos_im) / denom;
568            format_complex(Complex::new(re, im), 'i')
569        }
570    }
571}
572
573/// `IMCOT(complex)` — cotangent of complex number.
574pub fn imcot_fn(args: &[Value]) -> Value {
575    if let Some(err) = check_arity(args, 1, 1) {
576        return err;
577    }
578    match value_to_complex(args[0].clone()) {
579        Err(e) => e,
580        Ok(c) => {
581            let sin_re = c.re.sin() * c.im.cosh();
582            let sin_im = c.re.cos() * c.im.sinh();
583            let cos_re = c.re.cos() * c.im.cosh();
584            let cos_im = -(c.re.sin() * c.im.sinh());
585            let denom = sin_re * sin_re + sin_im * sin_im;
586            if denom == 0.0 {
587                return Value::Error(ErrorKind::DivByZero);
588            }
589            let re = (cos_re * sin_re + cos_im * sin_im) / denom;
590            let im = (cos_im * sin_re - cos_re * sin_im) / denom;
591            format_complex(Complex::new(re, im), 'i')
592        }
593    }
594}
595
596/// `IMCSC(complex)` — cosecant of complex number.
597pub fn imcsc_fn(args: &[Value]) -> Value {
598    if let Some(err) = check_arity(args, 1, 1) {
599        return err;
600    }
601    match value_to_complex(args[0].clone()) {
602        Err(e) => e,
603        Ok(c) => {
604            let sin_re = c.re.sin() * c.im.cosh();
605            let sin_im = c.re.cos() * c.im.sinh();
606            let denom = sin_re * sin_re + sin_im * sin_im;
607            if denom == 0.0 {
608                return Value::Error(ErrorKind::Num);
609            }
610            let re = sin_re / denom;
611            let im = -sin_im / denom;
612            format_complex(Complex::new(re, im), 'i')
613        }
614    }
615}
616
617/// `IMSEC(complex)` — secant of complex number.
618pub fn imsec_fn(args: &[Value]) -> Value {
619    if let Some(err) = check_arity(args, 1, 1) {
620        return err;
621    }
622    match value_to_complex(args[0].clone()) {
623        Err(e) => e,
624        Ok(c) => {
625            let cos_re = c.re.cos() * c.im.cosh();
626            let cos_im = -(c.re.sin() * c.im.sinh());
627            let denom = cos_re * cos_re + cos_im * cos_im;
628            if denom == 0.0 {
629                return Value::Error(ErrorKind::DivByZero);
630            }
631            let re = cos_re / denom;
632            let im = -cos_im / denom;
633            format_complex(Complex::new(re, im), 'i')
634        }
635    }
636}
637
638// ── Hyperbolic trig ────────────────────────────────────────────────────────────
639
640/// `IMSINH(complex)` — hyperbolic sine of complex number.
641pub fn imsinh_fn(args: &[Value]) -> Value {
642    if let Some(err) = check_arity(args, 1, 1) {
643        return err;
644    }
645    match value_to_complex(args[0].clone()) {
646        Err(e) => e,
647        Ok(c) => {
648            let re = c.re.sinh() * c.im.cos();
649            let im = c.re.cosh() * c.im.sin();
650            format_complex(Complex::new(re, im), 'i')
651        }
652    }
653}
654
655/// `IMCOSH(complex)` — hyperbolic cosine of complex number.
656pub fn imcosh_fn(args: &[Value]) -> Value {
657    if let Some(err) = check_arity(args, 1, 1) {
658        return err;
659    }
660    match value_to_complex(args[0].clone()) {
661        Err(e) => e,
662        Ok(c) => {
663            let re = c.re.cosh() * c.im.cos();
664            let im = c.re.sinh() * c.im.sin();
665            format_complex(Complex::new(re, im), 'i')
666        }
667    }
668}
669
670/// `IMTANH(complex)` — hyperbolic tangent of complex number.
671pub fn imtanh_fn(args: &[Value]) -> Value {
672    if let Some(err) = check_arity(args, 1, 1) {
673        return err;
674    }
675    match value_to_complex(args[0].clone()) {
676        Err(e) => e,
677        Ok(c) => {
678            let sinh_re = c.re.sinh() * c.im.cos();
679            let sinh_im = c.re.cosh() * c.im.sin();
680            let cosh_re = c.re.cosh() * c.im.cos();
681            let cosh_im = c.re.sinh() * c.im.sin();
682            let denom = cosh_re * cosh_re + cosh_im * cosh_im;
683            if denom == 0.0 {
684                return Value::Error(ErrorKind::DivByZero);
685            }
686            let re = (sinh_re * cosh_re + sinh_im * cosh_im) / denom;
687            let im = (sinh_im * cosh_re - sinh_re * cosh_im) / denom;
688            format_complex(Complex::new(re, im), 'i')
689        }
690    }
691}
692
693/// `IMCOTH(complex)` — hyperbolic cotangent of complex number.
694pub fn imcoth_fn(args: &[Value]) -> Value {
695    if let Some(err) = check_arity(args, 1, 1) {
696        return err;
697    }
698    match value_to_complex(args[0].clone()) {
699        Err(e) => e,
700        Ok(c) => {
701            let sinh_re = c.re.sinh() * c.im.cos();
702            let sinh_im = c.re.cosh() * c.im.sin();
703            let cosh_re = c.re.cosh() * c.im.cos();
704            let cosh_im = c.re.sinh() * c.im.sin();
705            let denom = sinh_re * sinh_re + sinh_im * sinh_im;
706            if denom == 0.0 {
707                return Value::Error(ErrorKind::DivByZero);
708            }
709            let re = (cosh_re * sinh_re + cosh_im * sinh_im) / denom;
710            let im = (cosh_im * sinh_re - cosh_re * sinh_im) / denom;
711            format_complex(Complex::new(re, im), 'i')
712        }
713    }
714}
715
716/// `IMCSCH(complex)` — hyperbolic cosecant of complex number.
717pub fn imcsch_fn(args: &[Value]) -> Value {
718    if let Some(err) = check_arity(args, 1, 1) {
719        return err;
720    }
721    match value_to_complex(args[0].clone()) {
722        Err(e) => e,
723        Ok(c) => {
724            let sinh_re = c.re.sinh() * c.im.cos();
725            let sinh_im = c.re.cosh() * c.im.sin();
726            let denom = sinh_re * sinh_re + sinh_im * sinh_im;
727            if denom == 0.0 {
728                return Value::Error(ErrorKind::DivByZero);
729            }
730            let re = sinh_re / denom;
731            let im = -sinh_im / denom;
732            format_complex(Complex::new(re, im), 'i')
733        }
734    }
735}
736
737/// `IMSECH(complex)` — hyperbolic secant of complex number.
738pub fn imsech_fn(args: &[Value]) -> Value {
739    if let Some(err) = check_arity(args, 1, 1) {
740        return err;
741    }
742    match value_to_complex(args[0].clone()) {
743        Err(e) => e,
744        Ok(c) => {
745            let cosh_re = c.re.cosh() * c.im.cos();
746            let cosh_im = c.re.sinh() * c.im.sin();
747            let denom = cosh_re * cosh_re + cosh_im * cosh_im;
748            if denom == 0.0 {
749                return Value::Error(ErrorKind::DivByZero);
750            }
751            let re = cosh_re / denom;
752            let im = -cosh_im / denom;
753            format_complex(Complex::new(re, im), 'i')
754        }
755    }
756}
757
758#[cfg(test)]
759mod tests;