use oxinum_core::OxiNumResult;
use oxinum_float::native::{BigFloat, RoundingMode};
use super::BigComplex;
const GUARD: u32 = 10;
fn bf_sinh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
let ex = x.exp(prec, mode)?;
let e_neg = (-x).exp(prec, mode)?;
let half = BigFloat::from_f64(0.5, prec)?;
let diff = &ex - &e_neg;
Ok((&diff * &half).with_precision(prec, mode))
}
fn bf_cosh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
let ex = x.exp(prec, mode)?;
let e_neg = (-x).exp(prec, mode)?;
let half = BigFloat::from_f64(0.5, prec)?;
let sum = &ex + &e_neg;
Ok((&sum * &half).with_precision(prec, mode))
}
fn bf_tanh(x: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
let sinh = bf_sinh(x, prec, mode)?;
let cosh = bf_cosh(x, prec, mode)?;
Ok(sinh
.div_ref_with_mode(&cosh, mode)?
.with_precision(prec, mode))
}
impl BigComplex {
pub fn sin(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let a = self.re.clone().with_precision(guard, mode);
let b = self.im.clone().with_precision(guard, mode);
let sin_a = a.sin(guard, mode)?;
let cos_a = a.cos(guard, mode)?;
let cosh_b = bf_cosh(&b, guard, mode)?;
let sinh_b = bf_sinh(&b, guard, mode)?;
let re = (&sin_a * &cosh_b).with_precision(prec, mode);
let im = (&cos_a * &sinh_b).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn cos(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let a = self.re.clone().with_precision(guard, mode);
let b = self.im.clone().with_precision(guard, mode);
let cos_a = a.cos(guard, mode)?;
let sin_a = a.sin(guard, mode)?;
let cosh_b = bf_cosh(&b, guard, mode)?;
let sinh_b = bf_sinh(&b, guard, mode)?;
let re = (&cos_a * &cosh_b).with_precision(prec, mode);
let prod = &sin_a * &sinh_b;
let im = (-&prod).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn sinh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let a = self.re.clone().with_precision(guard, mode);
let b = self.im.clone().with_precision(guard, mode);
let sinh_a = bf_sinh(&a, guard, mode)?;
let cosh_a = bf_cosh(&a, guard, mode)?;
let cos_b = b.cos(guard, mode)?;
let sin_b = b.sin(guard, mode)?;
let re = (&sinh_a * &cos_b).with_precision(prec, mode);
let im = (&cosh_a * &sin_b).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn cosh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let a = self.re.clone().with_precision(guard, mode);
let b = self.im.clone().with_precision(guard, mode);
let cosh_a = bf_cosh(&a, guard, mode)?;
let sinh_a = bf_sinh(&a, guard, mode)?;
let cos_b = b.cos(guard, mode)?;
let sin_b = b.sin(guard, mode)?;
let re = (&cosh_a * &cos_b).with_precision(prec, mode);
let im = (&sinh_a * &sin_b).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn tan(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let sin_z = self.sin(guard, mode)?;
let cos_z = self.cos(guard, mode)?;
sin_z.checked_div(&cos_z, prec, mode)
}
pub fn tanh(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
if self.im.is_zero() {
let a = self.re.clone().with_precision(guard, mode);
let t = bf_tanh(&a, guard, mode)?.with_precision(prec, mode);
return Ok(BigComplex {
re: t,
im: BigFloat::zero(prec),
});
}
let sinh_z = self.sinh(guard, mode)?;
let cosh_z = self.cosh(guard, mode)?;
sinh_z.checked_div(&cosh_z, prec, mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
const PREC: u32 = 80;
const MODE: RoundingMode = RoundingMode::HalfEven;
fn c(re: f64, im: f64) -> BigComplex {
BigComplex::from_f64(re, im, PREC).expect("finite parts")
}
#[test]
fn sin_zero_is_zero() {
let s = BigComplex::zero(PREC).sin(PREC, MODE).expect("sin");
assert!(s.re().to_f64().abs() < 1e-12, "re = {}", s.re().to_f64());
assert!(s.im().to_f64().abs() < 1e-12, "im = {}", s.im().to_f64());
}
#[test]
fn cos_zero_is_one() {
let cz = BigComplex::zero(PREC).cos(PREC, MODE).expect("cos");
assert!(
(cz.re().to_f64() - 1.0).abs() < 1e-12,
"re = {}",
cz.re().to_f64()
);
assert!(cz.im().to_f64().abs() < 1e-12, "im = {}", cz.im().to_f64());
}
#[test]
fn cosh_zero_is_one() {
let cz = BigComplex::zero(PREC).cosh(PREC, MODE).expect("cosh");
assert!(
(cz.re().to_f64() - 1.0).abs() < 1e-12,
"re = {}",
cz.re().to_f64()
);
assert!(cz.im().to_f64().abs() < 1e-12, "im = {}", cz.im().to_f64());
}
#[test]
fn sinh_zero_is_zero() {
let s = BigComplex::zero(PREC).sinh(PREC, MODE).expect("sinh");
assert!(s.re().to_f64().abs() < 1e-12);
assert!(s.im().to_f64().abs() < 1e-12);
}
#[test]
fn pythagorean_identity_general() {
let z = c(0.5, 0.3);
let s = z.sin(PREC, MODE).expect("sin");
let co = z.cos(PREC, MODE).expect("cos");
let s2 = s.mul_core(&s);
let c2 = co.mul_core(&co);
let sum = &s2 + &c2;
assert!(
(sum.re().to_f64() - 1.0).abs() < 1e-9,
"re(sum) = {}",
sum.re().to_f64()
);
assert!(
sum.im().to_f64().abs() < 1e-9,
"im(sum) = {}",
sum.im().to_f64()
);
}
#[test]
fn tan_zero_is_zero() {
let t = BigComplex::zero(PREC).tan(PREC, MODE).expect("tan");
assert!(t.re().to_f64().abs() < 1e-12);
assert!(t.im().to_f64().abs() < 1e-12);
}
#[test]
fn tanh_zero_is_zero() {
let t = BigComplex::zero(PREC).tanh(PREC, MODE).expect("tanh");
assert!(t.re().to_f64().abs() < 1e-12);
assert!(t.im().to_f64().abs() < 1e-12);
}
#[test]
fn tan_matches_real_axis() {
let t = c(0.4, 0.0).tan(PREC, MODE).expect("tan");
let expected = 0.4_f64.tan();
assert!(
(t.re().to_f64() - expected).abs() < 1e-9,
"re = {}",
t.re().to_f64()
);
assert!(t.im().to_f64().abs() < 1e-9, "im = {}", t.im().to_f64());
}
}