use oxinum_core::{OxiNumError, OxiNumResult};
use oxinum_float::native::{BigFloat, RoundingMode};
use super::BigComplex;
const GUARD: u32 = 10;
impl BigComplex {
pub fn abs(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
if self.is_zero() {
return Ok(BigFloat::zero(prec));
}
let guard = prec.saturating_add(GUARD);
let nrm = self.norm_sqr().with_precision(guard, mode);
Ok(nrm.sqrt(guard, mode)?.with_precision(prec, mode))
}
pub fn arg(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
let guard = prec.saturating_add(GUARD);
let re = self.re.clone().with_precision(guard, mode);
let im = self.im.clone().with_precision(guard, mode);
Ok(im.atan2(&re, guard, mode)?.with_precision(prec, mode))
}
pub fn exp(&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 exp_a = a.exp(guard, mode)?;
let cos_b = b.cos(guard, mode)?;
let sin_b = b.sin(guard, mode)?;
let re = (&exp_a * &cos_b).with_precision(prec, mode);
let im = (&exp_a * &sin_b).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn ln(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
if self.is_zero() {
return Err(OxiNumError::Domain("ln(0) is undefined".into()));
}
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 nrm = self.norm_sqr().with_precision(guard, mode);
let ln_nrm = nrm.ln(guard, mode)?;
let half = BigFloat::from_f64(0.5, guard)?;
let re = (&ln_nrm * &half).with_precision(prec, mode);
let im = b.atan2(&a, guard, mode)?.with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn sqrt(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
if self.is_zero() {
return Ok(BigComplex::zero(prec));
}
let guard = prec.saturating_add(GUARD);
if self.im.is_zero() {
let a = self.re.clone().with_precision(guard, mode);
if !a.is_sign_negative() {
let re = a.sqrt(guard, mode)?.with_precision(prec, mode);
return Ok(BigComplex {
re,
im: BigFloat::zero(prec),
});
} else {
let neg_a = (-&a).with_precision(guard, mode);
let im = neg_a.sqrt(guard, mode)?.with_precision(prec, mode);
return Ok(BigComplex {
re: BigFloat::zero(prec),
im,
});
}
}
let a = self.re.clone().with_precision(guard, mode);
let two = BigFloat::from_i64(2, guard, mode);
let m = {
let nrm = self.norm_sqr().with_precision(guard, mode);
nrm.sqrt(guard, mode)?
};
let zero = BigFloat::zero(guard);
let re_radicand = {
let s = &m + &a;
let r = s.div_ref_with_mode(&two, mode)?;
if r < zero {
zero.clone()
} else {
r
}
};
let re = re_radicand.sqrt(guard, mode)?.with_precision(prec, mode);
let im_radicand = {
let s = &m - &a;
let r = s.div_ref_with_mode(&two, mode)?;
if r < zero {
zero.clone()
} else {
r
}
};
let im_mag = im_radicand.sqrt(guard, mode)?;
let im = if self.im.is_sign_negative() {
(-&im_mag).with_precision(prec, mode)
} else {
im_mag.with_precision(prec, mode)
};
Ok(BigComplex { re, im })
}
pub fn pow(&self, w: &BigComplex, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
if self.is_zero() {
return if w.is_zero() {
Ok(BigComplex::one(prec, mode))
} else {
Ok(BigComplex::zero(prec))
};
}
let guard = prec.saturating_add(GUARD);
let ln_z = self.ln(guard, mode)?;
let prod = w.mul_core(&ln_z);
prod.exp(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 abs_three_four_is_five() {
let m = c(3.0, 4.0).abs(PREC, MODE).expect("abs");
assert!((m.to_f64() - 5.0).abs() < 1e-9, "|3+4i| = {}", m.to_f64());
}
#[test]
fn abs_zero_is_zero() {
let m = BigComplex::zero(PREC).abs(PREC, MODE).expect("abs");
assert!(m.is_zero());
}
#[test]
fn arg_of_i_is_half_pi() {
let a = BigComplex::i(PREC, MODE).arg(PREC, MODE).expect("arg");
assert!(
(a.to_f64() - std::f64::consts::FRAC_PI_2).abs() < 1e-9,
"arg(i) = {}",
a.to_f64()
);
}
#[test]
fn exp_i_pi_is_minus_one() {
let z = c(0.0, std::f64::consts::PI);
let e = z.exp(PREC, MODE).expect("exp");
assert!(
(e.re().to_f64() + 1.0).abs() < 1e-9,
"re = {}",
e.re().to_f64()
);
assert!(e.im().to_f64().abs() < 1e-9, "im = {}", e.im().to_f64());
}
#[test]
fn ln_minus_one_is_i_pi() {
let l = c(-1.0, 0.0).ln(PREC, MODE).expect("ln");
assert!(l.re().to_f64().abs() < 1e-9, "re = {}", l.re().to_f64());
assert!(
(l.im().to_f64() - std::f64::consts::PI).abs() < 1e-9,
"im = {}",
l.im().to_f64()
);
}
#[test]
fn ln_zero_is_domain_error() {
let l = BigComplex::zero(PREC).ln(PREC, MODE);
assert!(matches!(l, Err(OxiNumError::Domain(_))), "got {l:?}");
}
#[test]
fn sqrt_minus_one_is_i() {
let r = c(-1.0, 0.0).sqrt(PREC, MODE).expect("sqrt");
assert!(r.re().to_f64().abs() < 1e-9, "re = {}", r.re().to_f64());
assert!(
(r.im().to_f64() - 1.0).abs() < 1e-9,
"im = {}",
r.im().to_f64()
);
}
#[test]
fn sqrt_two_i_is_one_plus_i() {
let r = c(0.0, 2.0).sqrt(PREC, MODE).expect("sqrt");
assert!(
(r.re().to_f64() - 1.0).abs() < 1e-9,
"re = {}",
r.re().to_f64()
);
assert!(
(r.im().to_f64() - 1.0).abs() < 1e-9,
"im = {}",
r.im().to_f64()
);
}
#[test]
fn sqrt_positive_real() {
let r = c(4.0, 0.0).sqrt(PREC, MODE).expect("sqrt");
assert!((r.re().to_f64() - 2.0).abs() < 1e-9);
assert!(r.im().to_f64().abs() < 1e-12);
}
#[test]
fn sqrt_squared_roundtrip() {
let z = c(2.0, -3.0);
let r = z.sqrt(PREC, MODE).expect("sqrt");
let sq = r.mul_core(&r);
assert!(
(sq.re().to_f64() - 2.0).abs() < 1e-9,
"re = {}",
sq.re().to_f64()
);
assert!(
(sq.im().to_f64() + 3.0).abs() < 1e-9,
"im = {}",
sq.im().to_f64()
);
}
#[test]
fn pow_i_squared_is_minus_one() {
let r = BigComplex::i(PREC, MODE)
.pow(&c(2.0, 0.0), PREC, MODE)
.expect("pow");
assert!(
(r.re().to_f64() + 1.0).abs() < 1e-9,
"re = {}",
r.re().to_f64()
);
assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
}
#[test]
fn pow_zero_zero_is_one() {
let r = BigComplex::zero(PREC)
.pow(&BigComplex::zero(PREC), PREC, MODE)
.expect("pow");
assert!((r.re().to_f64() - 1.0).abs() < 1e-12);
assert!(r.im().to_f64().abs() < 1e-12);
}
#[test]
fn pow_zero_base_nonzero_exp_is_zero() {
let r = BigComplex::zero(PREC)
.pow(&c(2.0, 1.0), PREC, MODE)
.expect("pow");
assert!(r.is_zero());
}
}