use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use oxinum_core::{OxiNumError, OxiNumResult};
use crate::CBig;
#[inline]
fn add_core(a: &CBig, b: &CBig) -> CBig {
CBig {
re: &a.re + &b.re,
im: &a.im + &b.im,
}
}
#[inline]
fn sub_core(a: &CBig, b: &CBig) -> CBig {
CBig {
re: &a.re - &b.re,
im: &a.im - &b.im,
}
}
#[inline]
fn mul_core(a: &CBig, b: &CBig) -> CBig {
let re = (&a.re * &b.re) - (&a.im * &b.im);
let im = (&a.re * &b.im) + (&a.im * &b.re);
CBig { re, im }
}
#[inline]
fn div_numerators(a: &CBig, b: &CBig) -> (crate::DBig, crate::DBig) {
let num_re = (&a.re * &b.re) + (&a.im * &b.im);
let num_im = (&a.im * &b.re) - (&a.re * &b.im);
(num_re, num_im)
}
fn div_core(a: &CBig, b: &CBig) -> OxiNumResult<CBig> {
let denom = b.norm_sqr();
if b.is_zero() {
return Err(OxiNumError::DivByZero);
}
let (num_re, num_im) = div_numerators(a, b);
Ok(CBig {
re: &num_re / &denom,
im: &num_im / &denom,
})
}
#[inline]
fn div_op_core(a: &CBig, b: &CBig) -> CBig {
let denom = b.norm_sqr();
let (num_re, num_im) = div_numerators(a, b);
CBig {
re: &num_re / &denom,
im: &num_im / &denom,
}
}
impl CBig {
pub fn checked_div(&self, rhs: &CBig) -> OxiNumResult<CBig> {
div_core(self, rhs)
}
}
macro_rules! impl_binop {
($Trait:ident, $method:ident, $core:ident) => {
impl $Trait<&CBig> for &CBig {
type Output = CBig;
#[inline]
fn $method(self, rhs: &CBig) -> CBig {
$core(self, rhs)
}
}
impl $Trait<CBig> for CBig {
type Output = CBig;
#[inline]
fn $method(self, rhs: CBig) -> CBig {
$core(&self, &rhs)
}
}
impl $Trait<&CBig> for CBig {
type Output = CBig;
#[inline]
fn $method(self, rhs: &CBig) -> CBig {
$core(&self, rhs)
}
}
impl $Trait<CBig> for &CBig {
type Output = CBig;
#[inline]
fn $method(self, rhs: CBig) -> CBig {
$core(self, &rhs)
}
}
};
}
macro_rules! impl_assign {
($Trait:ident, $method:ident, $core:ident) => {
impl $Trait<&CBig> for CBig {
#[inline]
fn $method(&mut self, rhs: &CBig) {
*self = $core(&*self, rhs);
}
}
impl $Trait<CBig> for CBig {
#[inline]
fn $method(&mut self, rhs: CBig) {
*self = $core(&*self, &rhs);
}
}
};
}
impl_binop!(Add, add, add_core);
impl_binop!(Sub, sub, sub_core);
impl_binop!(Mul, mul, mul_core);
impl_binop!(Div, div, div_op_core);
impl_assign!(AddAssign, add_assign, add_core);
impl_assign!(SubAssign, sub_assign, sub_core);
impl_assign!(MulAssign, mul_assign, mul_core);
impl_assign!(DivAssign, div_assign, div_op_core);
impl Neg for CBig {
type Output = CBig;
#[inline]
fn neg(self) -> CBig {
CBig {
re: -self.re,
im: -self.im,
}
}
}
impl Neg for &CBig {
type Output = CBig;
#[inline]
fn neg(self) -> CBig {
CBig {
re: -&self.re,
im: -&self.im,
}
}
}
impl PartialEq for CBig {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.re == other.re && self.im == other.im
}
}
impl Default for CBig {
#[inline]
fn default() -> Self {
CBig::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c(re: f64, im: f64) -> CBig {
CBig::from_f64(re, im).expect("finite parts")
}
fn assert_parts(z: &CBig, re: &str, im: &str) {
assert_eq!(z.re().to_string(), re, "real part mismatch");
assert_eq!(z.im().to_string(), im, "imag part mismatch");
}
#[test]
fn add_component_wise() {
let sum = c(1.0, 2.0) + c(3.0, 4.0);
assert_parts(&sum, "4", "6");
}
#[test]
fn sub_component_wise() {
let diff = c(1.0, 2.0) - c(3.0, 4.0);
assert_parts(&diff, "-2", "-2");
}
#[test]
fn mul_hand_computed() {
let prod = c(1.0, 2.0) * c(3.0, 4.0);
assert_parts(&prod, "-5", "10");
}
#[test]
fn mul_i_squared_is_minus_one() {
let prod = CBig::i() * CBig::i();
assert_parts(&prod, "-1", "0");
}
#[test]
fn mul_one_plus_i_squared_is_two_i() {
let z = c(1.0, 1.0);
let sq = &z * &z;
assert_parts(&sq, "0", "2");
}
#[test]
fn div_self_is_one() {
let z = c(1.0, 1.0);
let q = &z / &z;
assert_parts(&q, "1", "0");
}
#[test]
fn div_general() {
let q = c(3.0, 4.0) / c(1.0, 2.0);
assert_parts(&q, "2.2", "-0.4");
}
#[test]
fn checked_div_self_is_one() {
let z = c(1.0, 1.0);
let q = z.checked_div(&z).expect("non-zero divisor");
assert_parts(&q, "1", "0");
}
#[test]
fn checked_div_by_zero_is_err() {
let z = c(1.0, 1.0);
assert!(matches!(
z.checked_div(&CBig::zero()),
Err(OxiNumError::DivByZero)
));
}
#[test]
#[should_panic]
fn div_operator_by_zero_panics() {
let z = c(1.0, 1.0);
let _ = z / CBig::zero();
}
#[test]
fn norm_sqr_exact() {
assert_eq!(c(3.0, 4.0).norm_sqr().to_string(), "25");
}
#[test]
fn default_is_zero() {
let d = CBig::default();
assert!(d.is_zero());
assert!(d == CBig::zero());
}
#[test]
fn partial_eq_component_wise() {
assert!(c(1.5, -2.0) == c(1.5, -2.0));
assert!(c(1.5, -2.0) != c(1.5, 2.0));
assert!(c(1.5, -2.0) != c(-1.5, -2.0));
}
#[test]
fn neg_owned_and_borrowed() {
let z = c(2.0, -3.0);
let n_ref = -&z;
assert_parts(&n_ref, "-2", "3");
let n_owned = -z;
assert_parts(&n_owned, "-2", "3");
}
#[test]
fn add_assign_accumulates() {
let mut a = c(1.0, 2.0);
a += c(3.0, 4.0);
assert_parts(&a, "4", "6");
a += &c(-4.0, -6.0);
assert!(a.is_zero());
}
#[test]
fn mul_assign_updates_in_place() {
let mut a = c(1.0, 1.0);
a *= &c(1.0, 1.0);
assert_parts(&a, "0", "2");
}
#[test]
fn sub_and_div_assign() {
let mut a = c(5.0, 5.0);
a -= c(2.0, 1.0);
assert_parts(&a, "3", "4");
a /= c(3.0, 4.0);
assert_parts(&a, "1", "0");
}
#[test]
fn ownership_variants_consistent() {
let a = c(1.0, 2.0);
let b = c(3.0, 4.0);
let target = c(4.0, 6.0);
assert!(&a + &b == target);
assert!(a.clone() + b.clone() == target);
assert!(a.clone() + &b == target);
assert!(&a + b.clone() == target);
}
}