use core::str::FromStr;
use oxinum_float::{atan2, cos, exp, ln, sin, sqrt};
use crate::{CBig, DBig, OxiNumError, OxiNumResult};
const GUARD: usize = 10;
fn make_dbig(s: &str) -> OxiNumResult<DBig> {
DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
}
impl CBig {
pub fn abs(&self, precision: usize) -> OxiNumResult<DBig> {
if self.is_zero() {
return make_dbig("0.0");
}
sqrt(&self.norm_sqr(), precision)
}
pub fn arg(&self, precision: usize) -> OxiNumResult<DBig> {
atan2(&self.im, &self.re, precision)
}
pub fn exp(&self, precision: usize) -> OxiNumResult<CBig> {
let guard = precision + GUARD;
let ea = exp(&self.re, guard)?;
let cosb = cos(&self.im, guard)?;
let sinb = sin(&self.im, guard)?;
Ok(CBig::from_parts(&ea * &cosb, &ea * &sinb))
}
pub fn ln(&self, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return Err(OxiNumError::Domain("ln(0) is undefined".into()));
}
let guard = precision + GUARD;
let ns = self.norm_sqr();
let re = &make_dbig("0.5")? * &ln(&ns, guard)?;
let im = atan2(&self.im, &self.re, guard)?;
Ok(CBig::from_parts(re, im))
}
pub fn sqrt(&self, precision: usize) -> OxiNumResult<CBig> {
let zero = DBig::from(0u32);
if self.is_zero() {
return Ok(CBig::zero());
}
if self.im == zero {
if self.re >= zero {
return Ok(CBig::from_parts(sqrt(&self.re, precision)?, zero));
}
let neg_a = -&self.re;
return Ok(CBig::from_parts(zero, sqrt(&neg_a, precision)?));
}
let guard = precision + GUARD;
let m = sqrt(&self.norm_sqr(), guard)?; let two = make_dbig("2.0")?;
let mut r_re = &(&m + &self.re) / &two;
if r_re < zero {
r_re = DBig::from(0u32);
}
let mut r_im = &(&m - &self.re) / &two;
if r_im < zero {
r_im = DBig::from(0u32);
}
let re = sqrt(&r_re, precision)?;
let im_mag = sqrt(&r_im, precision)?;
let im = if self.im < zero { -im_mag } else { im_mag };
Ok(CBig::from_parts(re, im))
}
pub fn pow(&self, w: &CBig, precision: usize) -> OxiNumResult<CBig> {
if self.is_zero() {
return if w.is_zero() {
Ok(CBig::one())
} else {
Ok(CBig::zero())
};
}
let guard = precision + GUARD;
let lz = self.ln(guard)?;
let prod = w * &lz;
prod.exp(precision)
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxinum_float::compute_pi;
fn pi(precision: usize) -> DBig {
compute_pi(precision)
}
#[test]
fn exp_i_pi_is_minus_one() {
let z = CBig::from_parts(DBig::from(0u32), pi(50));
let r = z.exp(40).expect("exp");
let (re, im) = r.to_f64_parts();
assert!((re + 1.0).abs() < 1e-12, "re = {re}");
assert!(im.abs() < 1e-12, "im = {im}");
}
#[test]
fn ln_minus_one_is_i_pi() {
let z = CBig::from_real(make_dbig("-1.0").expect("literal"));
let r = z.ln(40).expect("ln");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < 1e-12, "re = {re}");
assert!((im - std::f64::consts::PI).abs() < 1e-12, "im = {im}");
}
#[test]
fn ln_zero_is_domain_error() {
let r = CBig::zero().ln(40);
assert!(matches!(r, Err(OxiNumError::Domain(_))), "got {r:?}");
}
#[test]
fn sqrt_minus_one_is_i() {
let z = CBig::from_real(make_dbig("-1.0").expect("literal"));
let r = z.sqrt(40).expect("sqrt");
let (re, im) = r.to_f64_parts();
assert!(re.abs() < 1e-12, "re = {re}");
assert!((im - 1.0).abs() < 1e-12, "im = {im}");
}
#[test]
fn sqrt_two_i_is_one_plus_i() {
let z = CBig::from_parts(DBig::from(0u32), DBig::from(2u32));
let r = z.sqrt(40).expect("sqrt");
let (re, im) = r.to_f64_parts();
assert!((re - 1.0).abs() < 1e-12, "re = {re}");
assert!((im - 1.0).abs() < 1e-12, "im = {im}");
}
#[test]
fn sqrt_positive_real() {
let z = CBig::from_real(DBig::from(4u32));
let r = z.sqrt(40).expect("sqrt");
let (re, im) = r.to_f64_parts();
assert!((re - 2.0).abs() < 1e-12, "re = {re}");
assert!(im.abs() < 1e-12, "im = {im}");
}
#[test]
fn abs_three_four_is_five() {
let z = CBig::from_parts(DBig::from(3u32), DBig::from(4u32));
let m = z.abs(40).expect("abs");
assert!(m.to_string().starts_with('5'), "|3+4i| = {m}");
}
#[test]
fn abs_zero_is_zero() {
let m = CBig::zero().abs(40).expect("abs");
assert_eq!(m, DBig::from(0u32));
}
#[test]
fn arg_of_i_is_half_pi() {
let a = CBig::i().arg(40).expect("arg");
let v = a.to_f64();
assert!(
(v.value() - std::f64::consts::FRAC_PI_2).abs() < 1e-12,
"arg(i) = {a}"
);
}
#[test]
fn pow_i_squared_is_minus_one() {
let r = CBig::i()
.pow(&CBig::from_real(DBig::from(2u32)), 40)
.expect("pow");
let (re, im) = r.to_f64_parts();
assert!((re + 1.0).abs() < 1e-12, "re = {re}");
assert!(im.abs() < 1e-12, "im = {im}");
}
#[test]
fn pow_to_one_is_identity() {
let z = CBig::from_real(make_dbig("2.0").expect("literal"));
let r = z.pow(&CBig::one(), 40).expect("pow");
let (re, im) = r.to_f64_parts();
assert!((re - 2.0).abs() < 1e-12, "re = {re}");
assert!(im.abs() < 1e-12, "im = {im}");
}
#[test]
fn pow_zero_zero_is_one() {
let r = CBig::zero().pow(&CBig::zero(), 40).expect("pow");
assert!(r == CBig::one());
}
#[test]
fn pow_zero_base_nonzero_exp_is_zero() {
let r = CBig::zero()
.pow(&CBig::from_parts(DBig::from(2u32), DBig::from(1u32)), 40)
.expect("pow");
assert!(r.is_zero());
}
}