use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use oxinum_core::OxiNumResult;
use oxinum_float::native::RoundingMode;
use super::BigComplex;
impl BigComplex {
pub(crate) fn mul_core(&self, rhs: &BigComplex) -> BigComplex {
let a = &self.re;
let b = &self.im;
let c = &rhs.re;
let d = &rhs.im;
let ac = a * c;
let bd = b * d;
let re = &ac - &bd;
let ad = a * d;
let bc = b * c;
let im = &ad + &bc;
BigComplex { re, im }
}
pub(crate) fn div_core(
&self,
rhs: &BigComplex,
prec: u32,
mode: RoundingMode,
) -> OxiNumResult<BigComplex> {
if rhs.is_zero() {
return Err(oxinum_core::OxiNumError::DivByZero);
}
let guard = prec.saturating_add(10);
let a = &self.re;
let b = &self.im;
let c = &rhs.re;
let d = &rhs.im;
let denom = rhs.norm_sqr();
let ac = a * c;
let bd = b * d;
let num_re = &ac + &bd;
let bc = b * c;
let ad = a * d;
let num_im = &bc - &ad;
let _ = guard;
let re = num_re
.div_ref_with_mode(&denom, mode)?
.with_precision(prec, mode);
let im = num_im
.div_ref_with_mode(&denom, mode)?
.with_precision(prec, mode);
Ok(BigComplex { re, im })
}
pub fn checked_div(
&self,
rhs: &BigComplex,
prec: u32,
mode: RoundingMode,
) -> OxiNumResult<BigComplex> {
self.div_core(rhs, prec, mode)
}
}
impl Add<&BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: &BigComplex) -> BigComplex {
BigComplex {
re: &self.re + &rhs.re,
im: &self.im + &rhs.im,
}
}
}
impl Add<BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: BigComplex) -> BigComplex {
(&self).add(&rhs)
}
}
impl Add<&BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: &BigComplex) -> BigComplex {
(&self).add(rhs)
}
}
impl Add<BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: BigComplex) -> BigComplex {
self.add(&rhs)
}
}
impl AddAssign<&BigComplex> for BigComplex {
#[inline]
fn add_assign(&mut self, rhs: &BigComplex) {
*self = (&*self).add(rhs);
}
}
impl AddAssign<BigComplex> for BigComplex {
#[inline]
fn add_assign(&mut self, rhs: BigComplex) {
*self = (&*self).add(&rhs);
}
}
impl Sub<&BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigComplex) -> BigComplex {
BigComplex {
re: &self.re - &rhs.re,
im: &self.im - &rhs.im,
}
}
}
impl Sub<BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigComplex) -> BigComplex {
(&self).sub(&rhs)
}
}
impl Sub<&BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigComplex) -> BigComplex {
(&self).sub(rhs)
}
}
impl Sub<BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigComplex) -> BigComplex {
self.sub(&rhs)
}
}
impl SubAssign<&BigComplex> for BigComplex {
#[inline]
fn sub_assign(&mut self, rhs: &BigComplex) {
*self = (&*self).sub(rhs);
}
}
impl SubAssign<BigComplex> for BigComplex {
#[inline]
fn sub_assign(&mut self, rhs: BigComplex) {
*self = (&*self).sub(&rhs);
}
}
impl Mul<&BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: &BigComplex) -> BigComplex {
self.mul_core(rhs)
}
}
impl Mul<BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: BigComplex) -> BigComplex {
self.mul_core(&rhs)
}
}
impl Mul<&BigComplex> for BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: &BigComplex) -> BigComplex {
self.mul_core(rhs)
}
}
impl Mul<BigComplex> for &BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: BigComplex) -> BigComplex {
self.mul_core(&rhs)
}
}
impl MulAssign<&BigComplex> for BigComplex {
#[inline]
fn mul_assign(&mut self, rhs: &BigComplex) {
*self = self.mul_core(rhs);
}
}
impl MulAssign<BigComplex> for BigComplex {
#[inline]
fn mul_assign(&mut self, rhs: BigComplex) {
*self = self.mul_core(&rhs);
}
}
impl Neg for &BigComplex {
type Output = BigComplex;
#[inline]
fn neg(self) -> BigComplex {
BigComplex {
re: -&self.re,
im: -&self.im,
}
}
}
impl Neg for BigComplex {
type Output = BigComplex;
#[inline]
fn neg(self) -> BigComplex {
(&self).neg()
}
}
impl PartialEq for BigComplex {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.re == other.re && self.im == other.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 add_sub_componentwise() {
let z = &c(1.0, 2.0) + &c(3.0, -4.0);
assert!((z.re().to_f64() - 4.0).abs() < 1e-12);
assert!((z.im().to_f64() + 2.0).abs() < 1e-12);
let w = &c(1.0, 2.0) - &c(3.0, -4.0);
assert!((w.re().to_f64() + 2.0).abs() < 1e-12);
assert!((w.im().to_f64() - 6.0).abs() < 1e-12);
}
#[test]
fn mul_basic() {
let z = &c(1.0, 2.0) * &c(3.0, 4.0);
assert!(
(z.re().to_f64() + 5.0).abs() < 1e-12,
"re = {}",
z.re().to_f64()
);
assert!(
(z.im().to_f64() - 10.0).abs() < 1e-12,
"im = {}",
z.im().to_f64()
);
}
#[test]
fn mul_i_squared() {
let one_plus_i = c(1.0, 1.0);
let z = &one_plus_i * &one_plus_i;
assert!(z.re().to_f64().abs() < 1e-12, "re = {}", z.re().to_f64());
assert!(
(z.im().to_f64() - 2.0).abs() < 1e-12,
"im = {}",
z.im().to_f64()
);
}
#[test]
fn checked_div_basic() {
let q = c(2.0, 0.0)
.checked_div(&c(1.0, 1.0), PREC, MODE)
.expect("non-zero divisor");
assert!(
(q.re().to_f64() - 1.0).abs() < 1e-12,
"re = {}",
q.re().to_f64()
);
assert!(
(q.im().to_f64() + 1.0).abs() < 1e-12,
"im = {}",
q.im().to_f64()
);
}
#[test]
fn checked_div_general() {
let q = c(1.0, 2.0)
.checked_div(&c(3.0, 4.0), PREC, MODE)
.expect("non-zero divisor");
assert!(
(q.re().to_f64() - 0.44).abs() < 1e-12,
"re = {}",
q.re().to_f64()
);
assert!(
(q.im().to_f64() - 0.08).abs() < 1e-12,
"im = {}",
q.im().to_f64()
);
}
#[test]
fn checked_div_by_zero_is_err() {
let q = c(1.0, 1.0).checked_div(&BigComplex::zero(PREC), PREC, MODE);
assert!(
matches!(q, Err(oxinum_core::OxiNumError::DivByZero)),
"expected DivByZero, got {q:?}"
);
}
#[test]
fn neg_and_assign_ops() {
let z = -&c(1.0, -2.0);
assert!((z.re().to_f64() + 1.0).abs() < 1e-12);
assert!((z.im().to_f64() - 2.0).abs() < 1e-12);
let mut a = c(1.0, 1.0);
a += &c(2.0, 3.0);
assert!((a.re().to_f64() - 3.0).abs() < 1e-12);
assert!((a.im().to_f64() - 4.0).abs() < 1e-12);
let mut b = c(5.0, 5.0);
b -= c(1.0, 2.0);
assert!((b.re().to_f64() - 4.0).abs() < 1e-12);
assert!((b.im().to_f64() - 3.0).abs() < 1e-12);
let mut m = c(1.0, 2.0);
m *= &c(3.0, 4.0);
assert!((m.re().to_f64() + 5.0).abs() < 1e-12);
assert!((m.im().to_f64() - 10.0).abs() < 1e-12);
}
#[test]
fn partial_eq_componentwise() {
assert_eq!(c(1.0, 2.0), c(1.0, 2.0));
assert_ne!(c(1.0, 2.0), c(1.0, 2.5));
}
}