use crate::types::Complex;
pub fn expm1(z: Complex) -> Complex {
if z.norm() < 1.0 {
let a = z.re;
let b = z.im;
let exp_1 = a.exp_m1();
let cos_1 = -2.0 * (0.5 * b).sin().powi(2);
Complex::new(exp_1 * cos_1 + exp_1 + cos_1, b.sin() * a.exp())
} else {
z.exp() - Complex::new(1.0, 0.0)
}
}
pub fn log1p(z: Complex) -> Complex {
let a = z.re;
let b = z.im;
if a.abs() < 0.5 && b.abs() < 0.5 {
let re = 0.5 * (a * a + 2.0 * a + b * b).ln_1p();
Complex::new(re, (Complex::new(1.0, 0.0) + z).arg())
} else {
(Complex::new(1.0, 0.0) + z).ln()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Real;
const EPS: Real = f64::EPSILON;
#[test]
fn expm1_tiny_beats_naive() {
let z = Complex::new(1e-10, 1e-10);
let got = expm1(z);
let naive = z.exp() - Complex::new(1.0, 0.0);
let re_ref = 1e-10;
let im_ref = 1e-10 + 1e-20;
assert!((got.re - re_ref).abs() <= 4.0 * EPS * re_ref.abs());
assert!((got.im - im_ref).abs() <= 4.0 * EPS * im_ref.abs());
assert!((naive.re - re_ref).abs() > 1e3 * (got.re - re_ref).abs());
}
#[test]
fn log1p_tiny_beats_naive() {
let z = Complex::new(1e-10, 0.0);
let got = log1p(z);
let naive = (Complex::new(1.0, 0.0) + z).ln();
let re_ref = 1e-10 - 0.5 * 1e-10 * 1e-10;
assert!((got.re - re_ref).abs() <= 4.0 * EPS * re_ref.abs());
assert!((naive.re - re_ref).abs() > 1e3 * (got.re - re_ref).abs());
}
#[test]
fn expm1_log1p_match_naive_far_from_origin() {
let z = Complex::new(1.5, 0.8);
let e = expm1(z);
let e_naive = z.exp() - Complex::new(1.0, 0.0);
assert!((e - e_naive).norm() <= 8.0 * EPS * e.norm());
let l = log1p(z);
let l_naive = (Complex::new(1.0, 0.0) + z).ln();
assert!((l - l_naive).norm() <= 8.0 * EPS * l.norm());
}
}