use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use oxinum_core::{OxiNumError, OxiNumResult};
use oxinum_float::DBig;
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,
}
}
#[inline]
fn i64_to_exact_dbig(n: i64) -> DBig {
use oxinum_float::precision::with_precision;
with_precision(&DBig::from(n), 0)
}
#[inline]
fn add_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
CBig {
re: &z.re + d,
im: z.im.clone(),
}
}
#[inline]
fn sub_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
CBig {
re: &z.re - d,
im: z.im.clone(),
}
}
#[inline]
fn rsub_scalar_dbig(d: &DBig, z: &CBig) -> CBig {
CBig {
re: d - &z.re,
im: -z.im.clone(),
}
}
#[inline]
fn mul_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
CBig {
re: &z.re * d,
im: &z.im * d,
}
}
#[inline]
fn div_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
CBig {
re: &z.re / d,
im: &z.im / d,
}
}
#[inline]
fn add_scalar_i64(z: &CBig, n: i64) -> CBig {
add_scalar_dbig(z, &i64_to_exact_dbig(n))
}
#[inline]
fn sub_scalar_i64(z: &CBig, n: i64) -> CBig {
sub_scalar_dbig(z, &i64_to_exact_dbig(n))
}
#[inline]
fn rsub_scalar_i64(n: i64, z: &CBig) -> CBig {
rsub_scalar_dbig(&i64_to_exact_dbig(n), z)
}
#[inline]
fn mul_scalar_i64(z: &CBig, n: i64) -> CBig {
mul_scalar_dbig(z, &i64_to_exact_dbig(n))
}
#[inline]
fn div_scalar_i64(z: &CBig, n: i64) -> CBig {
div_scalar_dbig(z, &i64_to_exact_dbig(n))
}
impl Add<DBig> for CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: DBig) -> CBig {
add_scalar_dbig(&self, &rhs)
}
}
impl Add<&DBig> for CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: &DBig) -> CBig {
add_scalar_dbig(&self, rhs)
}
}
impl Add<DBig> for &CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: DBig) -> CBig {
add_scalar_dbig(self, &rhs)
}
}
impl Add<&DBig> for &CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: &DBig) -> CBig {
add_scalar_dbig(self, rhs)
}
}
impl AddAssign<DBig> for CBig {
#[inline]
fn add_assign(&mut self, rhs: DBig) {
*self = add_scalar_dbig(&*self, &rhs);
}
}
impl AddAssign<&DBig> for CBig {
#[inline]
fn add_assign(&mut self, rhs: &DBig) {
*self = add_scalar_dbig(&*self, rhs);
}
}
impl Sub<DBig> for CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: DBig) -> CBig {
sub_scalar_dbig(&self, &rhs)
}
}
impl Sub<&DBig> for CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: &DBig) -> CBig {
sub_scalar_dbig(&self, rhs)
}
}
impl Sub<DBig> for &CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: DBig) -> CBig {
sub_scalar_dbig(self, &rhs)
}
}
impl Sub<&DBig> for &CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: &DBig) -> CBig {
sub_scalar_dbig(self, rhs)
}
}
impl SubAssign<DBig> for CBig {
#[inline]
fn sub_assign(&mut self, rhs: DBig) {
*self = sub_scalar_dbig(&*self, &rhs);
}
}
impl SubAssign<&DBig> for CBig {
#[inline]
fn sub_assign(&mut self, rhs: &DBig) {
*self = sub_scalar_dbig(&*self, rhs);
}
}
impl Sub<CBig> for DBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: CBig) -> CBig {
rsub_scalar_dbig(&self, &rhs)
}
}
impl Sub<&CBig> for DBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: &CBig) -> CBig {
rsub_scalar_dbig(&self, rhs)
}
}
impl Sub<CBig> for &DBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: CBig) -> CBig {
rsub_scalar_dbig(self, &rhs)
}
}
impl Sub<&CBig> for &DBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: &CBig) -> CBig {
rsub_scalar_dbig(self, rhs)
}
}
impl Mul<DBig> for CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: DBig) -> CBig {
mul_scalar_dbig(&self, &rhs)
}
}
impl Mul<&DBig> for CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: &DBig) -> CBig {
mul_scalar_dbig(&self, rhs)
}
}
impl Mul<DBig> for &CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: DBig) -> CBig {
mul_scalar_dbig(self, &rhs)
}
}
impl Mul<&DBig> for &CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: &DBig) -> CBig {
mul_scalar_dbig(self, rhs)
}
}
impl MulAssign<DBig> for CBig {
#[inline]
fn mul_assign(&mut self, rhs: DBig) {
*self = mul_scalar_dbig(&*self, &rhs);
}
}
impl MulAssign<&DBig> for CBig {
#[inline]
fn mul_assign(&mut self, rhs: &DBig) {
*self = mul_scalar_dbig(&*self, rhs);
}
}
impl Div<DBig> for CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: DBig) -> CBig {
div_scalar_dbig(&self, &rhs)
}
}
impl Div<&DBig> for CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: &DBig) -> CBig {
div_scalar_dbig(&self, rhs)
}
}
impl Div<DBig> for &CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: DBig) -> CBig {
div_scalar_dbig(self, &rhs)
}
}
impl Div<&DBig> for &CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: &DBig) -> CBig {
div_scalar_dbig(self, rhs)
}
}
impl DivAssign<DBig> for CBig {
#[inline]
fn div_assign(&mut self, rhs: DBig) {
*self = div_scalar_dbig(&*self, &rhs);
}
}
impl DivAssign<&DBig> for CBig {
#[inline]
fn div_assign(&mut self, rhs: &DBig) {
*self = div_scalar_dbig(&*self, rhs);
}
}
impl Add<i64> for CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: i64) -> CBig {
add_scalar_i64(&self, rhs)
}
}
impl Add<i64> for &CBig {
type Output = CBig;
#[inline]
fn add(self, rhs: i64) -> CBig {
add_scalar_i64(self, rhs)
}
}
impl AddAssign<i64> for CBig {
#[inline]
fn add_assign(&mut self, rhs: i64) {
*self = add_scalar_i64(&*self, rhs);
}
}
impl Sub<i64> for CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: i64) -> CBig {
sub_scalar_i64(&self, rhs)
}
}
impl Sub<i64> for &CBig {
type Output = CBig;
#[inline]
fn sub(self, rhs: i64) -> CBig {
sub_scalar_i64(self, rhs)
}
}
impl SubAssign<i64> for CBig {
#[inline]
fn sub_assign(&mut self, rhs: i64) {
*self = sub_scalar_i64(&*self, rhs);
}
}
impl Sub<CBig> for i64 {
type Output = CBig;
#[inline]
fn sub(self, rhs: CBig) -> CBig {
rsub_scalar_i64(self, &rhs)
}
}
impl Sub<&CBig> for i64 {
type Output = CBig;
#[inline]
fn sub(self, rhs: &CBig) -> CBig {
rsub_scalar_i64(self, rhs)
}
}
impl Mul<i64> for CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: i64) -> CBig {
mul_scalar_i64(&self, rhs)
}
}
impl Mul<i64> for &CBig {
type Output = CBig;
#[inline]
fn mul(self, rhs: i64) -> CBig {
mul_scalar_i64(self, rhs)
}
}
impl MulAssign<i64> for CBig {
#[inline]
fn mul_assign(&mut self, rhs: i64) {
*self = mul_scalar_i64(&*self, rhs);
}
}
impl Div<i64> for CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: i64) -> CBig {
div_scalar_i64(&self, rhs)
}
}
impl Div<i64> for &CBig {
type Output = CBig;
#[inline]
fn div(self, rhs: i64) -> CBig {
div_scalar_i64(self, rhs)
}
}
impl DivAssign<i64> for CBig {
#[inline]
fn div_assign(&mut self, rhs: i64) {
*self = div_scalar_i64(&*self, rhs);
}
}
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);
}
#[test]
fn add_dbig_scalar() {
let z = CBig::from_f64(1.0, 2.0).expect("ok");
let d = DBig::from(3u32);
let r = z + &d;
assert_eq!(r.re().to_string(), "4");
assert_eq!(r.im().to_string(), "2");
}
#[test]
fn mul_dbig_scalar() {
let z = CBig::i(); let d = DBig::from(3u32);
let r = &z * &d;
assert_eq!(r.re().to_string(), "0");
assert_eq!(r.im().to_string(), "3");
}
#[test]
fn div_dbig_scalar() {
let z = CBig::from_f64(6.0, 4.0).expect("ok");
let d = DBig::from(2u32);
let r = z / d;
assert_eq!(r.re().to_string(), "3");
assert_eq!(r.im().to_string(), "2");
}
#[test]
fn sub_reversed_dbig_minus_z() {
let z = CBig::from_f64(3.0, 2.0).expect("ok");
let d = DBig::from(5u32);
let r = d - &z;
assert_eq!(r.re().to_string(), "2");
assert_eq!(r.im().to_string(), "-2");
}
#[test]
fn dbig_scalar_assign_ops() {
let mut z = CBig::from_f64(1.0, 2.0).expect("ok");
z += DBig::from(3u32);
assert_eq!(z.re().to_string(), "4");
assert_eq!(z.im().to_string(), "2");
z -= &DBig::from(1u32);
assert_eq!(z.re().to_string(), "3");
z *= DBig::from(2u32);
assert_eq!(z.re().to_string(), "6");
assert_eq!(z.im().to_string(), "4");
z /= &DBig::from(2u32);
assert_eq!(z.re().to_string(), "3");
assert_eq!(z.im().to_string(), "2");
}
#[test]
fn mul_i64_scalar() {
let z = CBig::i(); let r = z * 5i64;
assert_eq!(r.re().to_string(), "0");
assert_eq!(r.im().to_string(), "5");
}
#[test]
fn add_i64_scalar_assign() {
let mut z = CBig::from_f64(1.0, 0.0).expect("ok");
z += 4i64;
assert_eq!(z.re().to_string(), "5");
assert_eq!(z.im().to_string(), "0");
}
#[test]
fn i64_scalar_product_is_exact() {
let z: CBig = (3i64, 4i64).into();
let r = z * 5i64;
assert_eq!(r.re().to_string(), "15");
assert_eq!(r.im().to_string(), "20");
}
#[test]
fn sub_i64_scalar() {
let z = CBig::from_f64(5.0, 3.0).expect("ok");
let r = z - 2i64;
assert_eq!(r.re().to_string(), "3");
assert_eq!(r.im().to_string(), "3");
}
#[test]
fn sub_reversed_i64_minus_z() {
let z = CBig::from_f64(3.0, 2.0).expect("ok");
let r = 5i64 - &z;
assert_eq!(r.re().to_string(), "2");
assert_eq!(r.im().to_string(), "-2");
}
#[test]
fn div_i64_scalar() {
let z = CBig::from_f64(6.0, 4.0).expect("ok");
let r = z / 2i64;
assert_eq!(r.re().to_string(), "3");
assert_eq!(r.im().to_string(), "2");
}
#[test]
fn i64_scalar_ref_ops_consistent() {
let z = CBig::from_f64(2.0, 3.0).expect("ok");
let r1 = &z * 4i64;
let r2 = z.clone() * 4i64;
assert!(r1 == r2);
}
}