use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use oxinum_core::OxiNumResult;
use oxinum_float::native::{BigFloat, 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
}
}
#[inline]
fn add_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
BigComplex {
re: &z.re + d,
im: z.im.clone(),
}
}
#[inline]
fn sub_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
BigComplex {
re: &z.re - d,
im: z.im.clone(),
}
}
#[inline]
fn rsub_scalar_bf(d: &BigFloat, z: &BigComplex) -> BigComplex {
BigComplex {
re: d - &z.re,
im: -&z.im,
}
}
#[inline]
fn mul_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
BigComplex {
re: &z.re * d,
im: &z.im * d,
}
}
#[inline]
fn bf_from_i64_at(n: i64, prec: u32) -> BigFloat {
BigFloat::from_i64(n, prec, RoundingMode::HalfEven)
}
#[inline]
fn add_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
add_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
}
#[inline]
fn sub_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
sub_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
}
#[inline]
fn rsub_scalar_i64(n: i64, z: &BigComplex) -> BigComplex {
rsub_scalar_bf(&bf_from_i64_at(n, z.re.precision()), z)
}
#[inline]
fn mul_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
mul_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
}
impl Add<BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: BigFloat) -> BigComplex {
add_scalar_bf(&self, &rhs)
}
}
impl Add<&BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: &BigFloat) -> BigComplex {
add_scalar_bf(&self, rhs)
}
}
impl Add<BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: BigFloat) -> BigComplex {
add_scalar_bf(self, &rhs)
}
}
impl Add<&BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: &BigFloat) -> BigComplex {
add_scalar_bf(self, rhs)
}
}
impl AddAssign<BigFloat> for BigComplex {
#[inline]
fn add_assign(&mut self, rhs: BigFloat) {
*self = add_scalar_bf(self, &rhs);
}
}
impl AddAssign<&BigFloat> for BigComplex {
#[inline]
fn add_assign(&mut self, rhs: &BigFloat) {
*self = add_scalar_bf(self, rhs);
}
}
impl Sub<BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigFloat) -> BigComplex {
sub_scalar_bf(&self, &rhs)
}
}
impl Sub<&BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigFloat) -> BigComplex {
sub_scalar_bf(&self, rhs)
}
}
impl Sub<BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigFloat) -> BigComplex {
sub_scalar_bf(self, &rhs)
}
}
impl Sub<&BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigFloat) -> BigComplex {
sub_scalar_bf(self, rhs)
}
}
impl SubAssign<BigFloat> for BigComplex {
#[inline]
fn sub_assign(&mut self, rhs: BigFloat) {
*self = sub_scalar_bf(self, &rhs);
}
}
impl SubAssign<&BigFloat> for BigComplex {
#[inline]
fn sub_assign(&mut self, rhs: &BigFloat) {
*self = sub_scalar_bf(self, rhs);
}
}
impl Sub<BigComplex> for BigFloat {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigComplex) -> BigComplex {
rsub_scalar_bf(&self, &rhs)
}
}
impl Sub<&BigComplex> for BigFloat {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigComplex) -> BigComplex {
rsub_scalar_bf(&self, rhs)
}
}
impl Sub<BigComplex> for &BigFloat {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigComplex) -> BigComplex {
rsub_scalar_bf(self, &rhs)
}
}
impl Sub<&BigComplex> for &BigFloat {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigComplex) -> BigComplex {
rsub_scalar_bf(self, rhs)
}
}
impl Mul<BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: BigFloat) -> BigComplex {
mul_scalar_bf(&self, &rhs)
}
}
impl Mul<&BigFloat> for BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: &BigFloat) -> BigComplex {
mul_scalar_bf(&self, rhs)
}
}
impl Mul<BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: BigFloat) -> BigComplex {
mul_scalar_bf(self, &rhs)
}
}
impl Mul<&BigFloat> for &BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: &BigFloat) -> BigComplex {
mul_scalar_bf(self, rhs)
}
}
impl MulAssign<BigFloat> for BigComplex {
#[inline]
fn mul_assign(&mut self, rhs: BigFloat) {
*self = mul_scalar_bf(self, &rhs);
}
}
impl MulAssign<&BigFloat> for BigComplex {
#[inline]
fn mul_assign(&mut self, rhs: &BigFloat) {
*self = mul_scalar_bf(self, rhs);
}
}
impl Add<i64> for BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: i64) -> BigComplex {
add_scalar_i64(&self, rhs)
}
}
impl Add<i64> for &BigComplex {
type Output = BigComplex;
#[inline]
fn add(self, rhs: i64) -> BigComplex {
add_scalar_i64(self, rhs)
}
}
impl AddAssign<i64> for BigComplex {
#[inline]
fn add_assign(&mut self, rhs: i64) {
*self = add_scalar_i64(self, rhs);
}
}
impl Sub<i64> for BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: i64) -> BigComplex {
sub_scalar_i64(&self, rhs)
}
}
impl Sub<i64> for &BigComplex {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: i64) -> BigComplex {
sub_scalar_i64(self, rhs)
}
}
impl SubAssign<i64> for BigComplex {
#[inline]
fn sub_assign(&mut self, rhs: i64) {
*self = sub_scalar_i64(self, rhs);
}
}
impl Sub<BigComplex> for i64 {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: BigComplex) -> BigComplex {
rsub_scalar_i64(self, &rhs)
}
}
impl Sub<&BigComplex> for i64 {
type Output = BigComplex;
#[inline]
fn sub(self, rhs: &BigComplex) -> BigComplex {
rsub_scalar_i64(self, rhs)
}
}
impl Mul<i64> for BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: i64) -> BigComplex {
mul_scalar_i64(&self, rhs)
}
}
impl Mul<i64> for &BigComplex {
type Output = BigComplex;
#[inline]
fn mul(self, rhs: i64) -> BigComplex {
mul_scalar_i64(self, rhs)
}
}
impl MulAssign<i64> for BigComplex {
#[inline]
fn mul_assign(&mut self, rhs: i64) {
*self = mul_scalar_i64(self, rhs);
}
}
#[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));
}
#[test]
fn add_bigfloat_scalar() {
let z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
let d = BigFloat::from_i64(3, PREC, MODE);
let r = &z + &d;
assert!((r.re().to_f64() - 4.0).abs() < 1e-12);
assert!((r.im().to_f64() - 2.0).abs() < 1e-12);
}
#[test]
fn sub_bigfloat_scalar() {
let z = BigComplex::from_f64(5.0, 3.0, PREC).expect("ok");
let d = BigFloat::from_i64(2, PREC, MODE);
let r = &z - &d;
assert!((r.re().to_f64() - 3.0).abs() < 1e-12);
assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
}
#[test]
fn mul_bigfloat_scalar() {
let z = BigComplex::from_f64(0.0, 1.0, PREC).expect("i");
let d = BigFloat::from_i64(3, PREC, MODE);
let r = z * d;
assert!(r.re().to_f64().abs() < 1e-12);
assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
}
#[test]
fn sub_reversed_bigfloat_minus_z() {
let z = BigComplex::from_f64(3.0, 2.0, PREC).expect("ok");
let d = BigFloat::from_i64(5, PREC, MODE);
let r = d - &z;
assert!((r.re().to_f64() - 2.0).abs() < 1e-12);
assert!((r.im().to_f64() + 2.0).abs() < 1e-12);
}
#[test]
fn bigfloat_scalar_assign_ops() {
let mut z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
let d3 = BigFloat::from_i64(3, PREC, MODE);
z += &d3;
assert!((z.re().to_f64() - 4.0).abs() < 1e-12);
assert!((z.im().to_f64() - 2.0).abs() < 1e-12);
let d1 = BigFloat::from_i64(1, PREC, MODE);
z -= d1;
assert!((z.re().to_f64() - 3.0).abs() < 1e-12);
let d2 = BigFloat::from_i64(2, PREC, MODE);
z *= &d2;
assert!((z.re().to_f64() - 6.0).abs() < 1e-12);
assert!((z.im().to_f64() - 4.0).abs() < 1e-12);
}
#[test]
fn add_i64_scalar() {
let z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
let r = &z + 4i64;
assert!((r.re().to_f64() - 5.0).abs() < 1e-12);
assert!((r.im().to_f64() - 2.0).abs() < 1e-12);
}
#[test]
fn sub_i64_scalar() {
let z = BigComplex::from_f64(5.0, 3.0, PREC).expect("ok");
let r = z - 2i64;
assert!((r.re().to_f64() - 3.0).abs() < 1e-12);
assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
}
#[test]
fn mul_i64_scalar_assign() {
let mut z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
z *= 3i64;
assert!((z.re().to_f64() - 3.0).abs() < 1e-12);
assert!((z.im().to_f64() - 6.0).abs() < 1e-12);
}
#[test]
fn sub_reversed_i64_minus_z() {
let z = BigComplex::from_f64(3.0, 2.0, PREC).expect("ok");
let r = 5i64 - &z;
assert!((r.re().to_f64() - 2.0).abs() < 1e-12);
assert!((r.im().to_f64() + 2.0).abs() < 1e-12);
}
#[test]
fn i64_scalar_ref_and_owned_consistent() {
let z = BigComplex::from_f64(2.0, 3.0, PREC).expect("ok");
let r1 = &z * 4i64;
let r2 = z.clone() * 4i64;
assert_eq!(r1, r2);
}
}