use oxinum_core::OxiNumError;
use oxinum_core::OxiNumResult;
use oxinum_float::native::{BigFloat, RoundingMode};
use super::BigComplex;
const GUARD: u32 = 10;
fn exp_by_squaring_native(mut base: BigComplex, mut n: u32) -> BigComplex {
let prec = base.re.precision();
let mode = RoundingMode::HalfEven;
let mut result = BigComplex::one(prec, mode);
while n > 0 {
if n & 1 == 1 {
result = result.mul_core(&base);
}
base = base.mul_core(&base);
n >>= 1;
}
result
}
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)
}
pub fn from_polar(
r: &BigFloat,
theta: &BigFloat,
prec: u32,
mode: RoundingMode,
) -> OxiNumResult<BigComplex> {
let guard = prec.saturating_add(GUARD);
let r_g = r.clone().with_precision(guard, mode);
let theta_g = theta.clone().with_precision(guard, mode);
let cos_t = theta_g.cos(guard, mode)?;
let sin_t = theta_g.sin(guard, mode)?;
let re = (&r_g * &cos_t).with_precision(prec, mode);
let im = (&r_g * &sin_t).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn to_polar(&self, prec: u32, mode: RoundingMode) -> OxiNumResult<(BigFloat, BigFloat)> {
let mag = self.abs(prec, mode)?;
let arg = self.arg(prec, mode)?;
Ok((mag, arg))
}
pub fn powi(&self, n: i32, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
if n == 0 {
return Ok(BigComplex::one(prec, mode));
}
let negative = n < 0;
let abs_n = n.unsigned_abs();
let result = exp_by_squaring_native(self.clone(), abs_n);
if negative {
BigComplex::one(prec, mode)
.checked_div(&result, prec, mode)
.map_err(|_| OxiNumError::DivByZero)
} else {
Ok(result)
}
}
pub fn powf(&self, exp: &BigFloat, prec: u32, mode: RoundingMode) -> OxiNumResult<BigComplex> {
if self.is_zero() {
if exp.is_zero() {
return Ok(BigComplex::one(prec, mode));
} else {
return Ok(BigComplex::zero(prec));
}
}
let guard = prec.saturating_add(GUARD);
let r = self.abs(guard, mode)?;
let theta = self.arg(guard, mode)?;
let exp_g = exp.clone().with_precision(guard, mode);
let rx = r.pow(&exp_g, guard, mode)?;
let x_theta = (&exp_g * &theta).with_precision(guard, mode);
let cos_val = x_theta.cos(guard, mode)?;
let sin_val = x_theta.sin(guard, mode)?;
let re = (&rx * &cos_val).with_precision(prec, mode);
let im = (&rx * &sin_val).with_precision(prec, mode);
Ok(BigComplex { re, im })
}
}
#[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());
}
#[test]
fn from_polar_two_half_pi_is_2i() {
let r = BigFloat::from_f64(2.0, PREC).expect("finite");
let half_pi = BigFloat::from_f64(std::f64::consts::FRAC_PI_2, PREC).expect("pi/2");
let z = BigComplex::from_polar(&r, &half_pi, PREC, MODE).expect("from_polar");
assert!(z.re().to_f64().abs() < 1e-9, "re = {}", z.re().to_f64());
assert!(
(z.im().to_f64() - 2.0).abs() < 1e-9,
"im = {}",
z.im().to_f64()
);
}
#[test]
fn to_polar_three_four_is_5_atan2_4_3() {
const P: u32 = 53;
let z = BigComplex::from_parts(
BigFloat::from_i64(3, P, MODE),
BigFloat::from_i64(4, P, MODE),
);
let (mag, arg) = z.to_polar(P, MODE).expect("to_polar");
assert!((mag.to_f64() - 5.0).abs() < 1e-9, "mag = {}", mag.to_f64());
let expected_arg = 4.0_f64.atan2(3.0);
assert!(
(arg.to_f64() - expected_arg).abs() < 1e-9,
"arg = {}",
arg.to_f64()
);
}
#[test]
fn from_polar_to_polar_roundtrip() {
const P: u32 = 53;
let z = BigComplex::from_parts(
BigFloat::from_i64(2, P, MODE),
BigFloat::from_i64(3, P, MODE),
);
let (mag, arg) = z.to_polar(P, MODE).expect("to_polar");
let z2 = BigComplex::from_polar(&mag, &arg, P, MODE).expect("from_polar");
assert!(
(z2.re().to_f64() - 2.0).abs() < 1e-9,
"re = {}",
z2.re().to_f64()
);
assert!(
(z2.im().to_f64() - 3.0).abs() < 1e-9,
"im = {}",
z2.im().to_f64()
);
}
#[test]
fn powi_zero_exponent_is_one() {
let z = c(1.5, 0.7);
let r = z.powi(0, PREC, MODE).expect("powi");
assert!(
(r.re().to_f64() - 1.0).abs() < 1e-12,
"re = {}",
r.re().to_f64()
);
assert!(r.im().to_f64().abs() < 1e-12, "im = {}", r.im().to_f64());
}
#[test]
fn powi_one_plus_i_squared() {
let z = c(1.0, 1.0);
let r = z.powi(2, PREC, MODE).expect("powi");
assert!(r.re().to_f64().abs() < 1e-9, "re = {}", r.re().to_f64());
assert!(
(r.im().to_f64() - 2.0).abs() < 1e-9,
"im = {}",
r.im().to_f64()
);
}
#[test]
fn powi_i_fourth_is_one() {
let r = BigComplex::i(PREC, MODE).powi(4, PREC, MODE).expect("powi");
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 powi_negative_one_is_reciprocal() {
let z = c(2.0, 0.0);
let r = z.powi(-1, PREC, MODE).expect("powi");
assert!(
(r.re().to_f64() - 0.5).abs() < 1e-9,
"re = {}",
r.re().to_f64()
);
assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
}
#[test]
fn powf_matches_powi_squared() {
let z = c(1.5, 0.7);
let exp = BigFloat::from_f64(2.0, PREC).expect("finite");
let r1 = z.powf(&exp, PREC, MODE).expect("powf");
let r2 = z.powi(2, PREC, MODE).expect("powi");
assert!(
(r1.re().to_f64() - r2.re().to_f64()).abs() < 1e-9,
"re: {} vs {}",
r1.re().to_f64(),
r2.re().to_f64()
);
assert!(
(r1.im().to_f64() - r2.im().to_f64()).abs() < 1e-9,
"im: {} vs {}",
r1.im().to_f64(),
r2.im().to_f64()
);
}
#[test]
fn powf_matches_pow_on_real() {
let z = c(2.0, 0.0);
let exp = BigFloat::from_f64(3.0, PREC).expect("finite");
let r = z.powf(&exp, PREC, MODE).expect("powf");
assert!(
(r.re().to_f64() - 8.0).abs() < 1e-9,
"re = {}",
r.re().to_f64()
);
assert!(r.im().to_f64().abs() < 1e-9, "im = {}", r.im().to_f64());
}
}