Skip to main content

oxinum_complex/
ops.rs

1//! Arithmetic operator implementations for [`crate::CBig`].
2//!
3//! Every binary operator (`Add`, `Sub`, `Mul`, `Div`) provides all four
4//! ownership variants — `&T op &T`, `T op T`, `T op &T`, `&T op T` — each
5//! producing an owned [`CBig`]. The matching `*Assign` traits are wired up for
6//! both owned and borrowed right-hand sides, and [`Neg`] is provided for owned
7//! and borrowed receivers.
8//!
9//! # Complex arithmetic
10//!
11//! With `a = a_re + a_im·i` and `b = b_re + b_im·i`:
12//!
13//! ```text
14//! a + b = (a_re + b_re) + (a_im + b_im)·i
15//! a − b = (a_re − b_re) + (a_im − b_im)·i
16//! a · b = (a_re·b_re − a_im·b_im) + (a_re·b_im + a_im·b_re)·i
17//! a / b = (a · conj(b)) / |b|²
18//!       = (a_re·b_re + a_im·b_im)/|b|² + (a_im·b_re − a_re·b_im)/|b|²·i
19//! ```
20//!
21//! # Zero-divisor policy
22//!
23//! Mirroring every sibling operator in this workspace (e.g.
24//! `oxinum_rational::native::BigRational`, whose `Div` panics on a zero
25//! divisor while `recip`/constructors return [`OxiNumError::DivByZero`]):
26//!
27//! * the [`Div`] **operator** panics on a zero divisor. The panic originates
28//!   inside `dashu-float`'s `DBig` `/`, which documents that "division by zero
29//!   … panic[s] instead of returning infinities" — so this file contains no
30//!   explicit `unwrap`/`expect`/`panic!`;
31//! * the no-panic [`CBig::checked_div`] returns [`OxiNumError::DivByZero`]
32//!   instead.
33
34use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
35
36use oxinum_core::{OxiNumError, OxiNumResult};
37use oxinum_float::DBig;
38
39use crate::CBig;
40
41// ---------------------------------------------------------------------------
42// Internal core helpers
43// ---------------------------------------------------------------------------
44
45/// Core `Add` body shared by all four ownership variants: component-wise.
46#[inline]
47fn add_core(a: &CBig, b: &CBig) -> CBig {
48    CBig {
49        re: &a.re + &b.re,
50        im: &a.im + &b.im,
51    }
52}
53
54/// Core `Sub` body shared by all four ownership variants: component-wise.
55#[inline]
56fn sub_core(a: &CBig, b: &CBig) -> CBig {
57    CBig {
58        re: &a.re - &b.re,
59        im: &a.im - &b.im,
60    }
61}
62
63/// Core `Mul` body: `(a_re·b_re − a_im·b_im) + (a_re·b_im + a_im·b_re)·i`.
64#[inline]
65fn mul_core(a: &CBig, b: &CBig) -> CBig {
66    let re = (&a.re * &b.re) - (&a.im * &b.im);
67    let im = (&a.re * &b.im) + (&a.im * &b.re);
68    CBig { re, im }
69}
70
71/// The unreduced numerators of `a / b`, i.e. the real and imaginary parts of
72/// `a · conj(b)`. The actual quotient divides each by `|b|²`.
73///
74/// Returned as `(num_re, num_im)` where
75/// `num_re = a_re·b_re + a_im·b_im` and `num_im = a_im·b_re − a_re·b_im`.
76/// Shared by [`div_core`] (which adds the explicit zero check) and the
77/// [`Div`] operator (which lets `DBig`'s `/` panic on a zero denominator).
78#[inline]
79fn div_numerators(a: &CBig, b: &CBig) -> (crate::DBig, crate::DBig) {
80    let num_re = (&a.re * &b.re) + (&a.im * &b.im);
81    let num_im = (&a.im * &b.re) - (&a.re * &b.im);
82    (num_re, num_im)
83}
84
85/// Core `Div` body with an explicit zero-divisor guard.
86///
87/// Computes `denom = |b|²`; if `b` is zero returns
88/// [`OxiNumError::DivByZero`]. Otherwise `denom` is guaranteed non-zero, so
89/// the two `DBig` divisions cannot panic.
90fn div_core(a: &CBig, b: &CBig) -> OxiNumResult<CBig> {
91    let denom = b.norm_sqr();
92    if b.is_zero() {
93        return Err(OxiNumError::DivByZero);
94    }
95    let (num_re, num_im) = div_numerators(a, b);
96    Ok(CBig {
97        re: &num_re / &denom,
98        im: &num_im / &denom,
99    })
100}
101
102/// Core `Div` body for the operator path: no early zero check.
103///
104/// When `b` is zero, `denom = |b|² = 0` and the `DBig` `/` panics, exactly as
105/// the sibling `BigRational` `/` operator does. This function therefore
106/// contains no explicit `unwrap`/`expect`/`panic!`.
107#[inline]
108fn div_op_core(a: &CBig, b: &CBig) -> CBig {
109    let denom = b.norm_sqr();
110    let (num_re, num_im) = div_numerators(a, b);
111    CBig {
112        re: &num_re / &denom,
113        im: &num_im / &denom,
114    }
115}
116
117// ---------------------------------------------------------------------------
118// Scalar cores — complex op real scalar, component-wise.
119// ---------------------------------------------------------------------------
120
121/// Convert an `i64` to an exact (unlimited-precision) `DBig`.
122///
123/// `DBig::from(n)` retains only the significant digits needed to render `n`,
124/// which causes subsequent arithmetic to round at that narrow precision. Setting
125/// precision `0` lifts that cap so that scalar products/sums stay exact.
126#[inline]
127fn i64_to_exact_dbig(n: i64) -> DBig {
128    use oxinum_float::precision::with_precision;
129    with_precision(&DBig::from(n), 0)
130}
131
132/// `z + d = (re + d, im)`.
133#[inline]
134fn add_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
135    CBig {
136        re: &z.re + d,
137        im: z.im.clone(),
138    }
139}
140
141/// `z - d = (re - d, im)`.
142#[inline]
143fn sub_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
144    CBig {
145        re: &z.re - d,
146        im: z.im.clone(),
147    }
148}
149
150/// `d - z = (d - re, -im)`.
151#[inline]
152fn rsub_scalar_dbig(d: &DBig, z: &CBig) -> CBig {
153    CBig {
154        re: d - &z.re,
155        im: -z.im.clone(),
156    }
157}
158
159/// `z * d = (re*d, im*d)`.
160#[inline]
161fn mul_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
162    CBig {
163        re: &z.re * d,
164        im: &z.im * d,
165    }
166}
167
168/// `z / d = (re/d, im/d)` — panics if `d` is zero (mirrors the `CBig` `Div` operator).
169#[inline]
170fn div_scalar_dbig(z: &CBig, d: &DBig) -> CBig {
171    CBig {
172        re: &z.re / d,
173        im: &z.im / d,
174    }
175}
176
177/// Scalar `i64` cores — delegate to the `DBig` variants via an exact conversion.
178#[inline]
179fn add_scalar_i64(z: &CBig, n: i64) -> CBig {
180    add_scalar_dbig(z, &i64_to_exact_dbig(n))
181}
182#[inline]
183fn sub_scalar_i64(z: &CBig, n: i64) -> CBig {
184    sub_scalar_dbig(z, &i64_to_exact_dbig(n))
185}
186#[inline]
187fn rsub_scalar_i64(n: i64, z: &CBig) -> CBig {
188    rsub_scalar_dbig(&i64_to_exact_dbig(n), z)
189}
190#[inline]
191fn mul_scalar_i64(z: &CBig, n: i64) -> CBig {
192    mul_scalar_dbig(z, &i64_to_exact_dbig(n))
193}
194#[inline]
195fn div_scalar_i64(z: &CBig, n: i64) -> CBig {
196    div_scalar_dbig(z, &i64_to_exact_dbig(n))
197}
198
199// ---------------------------------------------------------------------------
200// CBig op DBig — Add
201// ---------------------------------------------------------------------------
202
203impl Add<DBig> for CBig {
204    type Output = CBig;
205    #[inline]
206    fn add(self, rhs: DBig) -> CBig {
207        add_scalar_dbig(&self, &rhs)
208    }
209}
210
211impl Add<&DBig> for CBig {
212    type Output = CBig;
213    #[inline]
214    fn add(self, rhs: &DBig) -> CBig {
215        add_scalar_dbig(&self, rhs)
216    }
217}
218
219impl Add<DBig> for &CBig {
220    type Output = CBig;
221    #[inline]
222    fn add(self, rhs: DBig) -> CBig {
223        add_scalar_dbig(self, &rhs)
224    }
225}
226
227impl Add<&DBig> for &CBig {
228    type Output = CBig;
229    #[inline]
230    fn add(self, rhs: &DBig) -> CBig {
231        add_scalar_dbig(self, rhs)
232    }
233}
234
235impl AddAssign<DBig> for CBig {
236    #[inline]
237    fn add_assign(&mut self, rhs: DBig) {
238        *self = add_scalar_dbig(&*self, &rhs);
239    }
240}
241
242impl AddAssign<&DBig> for CBig {
243    #[inline]
244    fn add_assign(&mut self, rhs: &DBig) {
245        *self = add_scalar_dbig(&*self, rhs);
246    }
247}
248
249// ---------------------------------------------------------------------------
250// CBig op DBig — Sub
251// ---------------------------------------------------------------------------
252
253impl Sub<DBig> for CBig {
254    type Output = CBig;
255    #[inline]
256    fn sub(self, rhs: DBig) -> CBig {
257        sub_scalar_dbig(&self, &rhs)
258    }
259}
260
261impl Sub<&DBig> for CBig {
262    type Output = CBig;
263    #[inline]
264    fn sub(self, rhs: &DBig) -> CBig {
265        sub_scalar_dbig(&self, rhs)
266    }
267}
268
269impl Sub<DBig> for &CBig {
270    type Output = CBig;
271    #[inline]
272    fn sub(self, rhs: DBig) -> CBig {
273        sub_scalar_dbig(self, &rhs)
274    }
275}
276
277impl Sub<&DBig> for &CBig {
278    type Output = CBig;
279    #[inline]
280    fn sub(self, rhs: &DBig) -> CBig {
281        sub_scalar_dbig(self, rhs)
282    }
283}
284
285impl SubAssign<DBig> for CBig {
286    #[inline]
287    fn sub_assign(&mut self, rhs: DBig) {
288        *self = sub_scalar_dbig(&*self, &rhs);
289    }
290}
291
292impl SubAssign<&DBig> for CBig {
293    #[inline]
294    fn sub_assign(&mut self, rhs: &DBig) {
295        *self = sub_scalar_dbig(&*self, rhs);
296    }
297}
298
299// ---------------------------------------------------------------------------
300// DBig op CBig — reversed Sub (d - z)
301// ---------------------------------------------------------------------------
302
303impl Sub<CBig> for DBig {
304    type Output = CBig;
305    #[inline]
306    fn sub(self, rhs: CBig) -> CBig {
307        rsub_scalar_dbig(&self, &rhs)
308    }
309}
310
311impl Sub<&CBig> for DBig {
312    type Output = CBig;
313    #[inline]
314    fn sub(self, rhs: &CBig) -> CBig {
315        rsub_scalar_dbig(&self, rhs)
316    }
317}
318
319impl Sub<CBig> for &DBig {
320    type Output = CBig;
321    #[inline]
322    fn sub(self, rhs: CBig) -> CBig {
323        rsub_scalar_dbig(self, &rhs)
324    }
325}
326
327impl Sub<&CBig> for &DBig {
328    type Output = CBig;
329    #[inline]
330    fn sub(self, rhs: &CBig) -> CBig {
331        rsub_scalar_dbig(self, rhs)
332    }
333}
334
335// ---------------------------------------------------------------------------
336// CBig op DBig — Mul
337// ---------------------------------------------------------------------------
338
339impl Mul<DBig> for CBig {
340    type Output = CBig;
341    #[inline]
342    fn mul(self, rhs: DBig) -> CBig {
343        mul_scalar_dbig(&self, &rhs)
344    }
345}
346
347impl Mul<&DBig> for CBig {
348    type Output = CBig;
349    #[inline]
350    fn mul(self, rhs: &DBig) -> CBig {
351        mul_scalar_dbig(&self, rhs)
352    }
353}
354
355impl Mul<DBig> for &CBig {
356    type Output = CBig;
357    #[inline]
358    fn mul(self, rhs: DBig) -> CBig {
359        mul_scalar_dbig(self, &rhs)
360    }
361}
362
363impl Mul<&DBig> for &CBig {
364    type Output = CBig;
365    #[inline]
366    fn mul(self, rhs: &DBig) -> CBig {
367        mul_scalar_dbig(self, rhs)
368    }
369}
370
371impl MulAssign<DBig> for CBig {
372    #[inline]
373    fn mul_assign(&mut self, rhs: DBig) {
374        *self = mul_scalar_dbig(&*self, &rhs);
375    }
376}
377
378impl MulAssign<&DBig> for CBig {
379    #[inline]
380    fn mul_assign(&mut self, rhs: &DBig) {
381        *self = mul_scalar_dbig(&*self, rhs);
382    }
383}
384
385// ---------------------------------------------------------------------------
386// CBig op DBig — Div
387// ---------------------------------------------------------------------------
388
389impl Div<DBig> for CBig {
390    type Output = CBig;
391    #[inline]
392    fn div(self, rhs: DBig) -> CBig {
393        div_scalar_dbig(&self, &rhs)
394    }
395}
396
397impl Div<&DBig> for CBig {
398    type Output = CBig;
399    #[inline]
400    fn div(self, rhs: &DBig) -> CBig {
401        div_scalar_dbig(&self, rhs)
402    }
403}
404
405impl Div<DBig> for &CBig {
406    type Output = CBig;
407    #[inline]
408    fn div(self, rhs: DBig) -> CBig {
409        div_scalar_dbig(self, &rhs)
410    }
411}
412
413impl Div<&DBig> for &CBig {
414    type Output = CBig;
415    #[inline]
416    fn div(self, rhs: &DBig) -> CBig {
417        div_scalar_dbig(self, rhs)
418    }
419}
420
421impl DivAssign<DBig> for CBig {
422    #[inline]
423    fn div_assign(&mut self, rhs: DBig) {
424        *self = div_scalar_dbig(&*self, &rhs);
425    }
426}
427
428impl DivAssign<&DBig> for CBig {
429    #[inline]
430    fn div_assign(&mut self, rhs: &DBig) {
431        *self = div_scalar_dbig(&*self, rhs);
432    }
433}
434
435// ---------------------------------------------------------------------------
436// CBig op i64 — Add
437// ---------------------------------------------------------------------------
438
439impl Add<i64> for CBig {
440    type Output = CBig;
441    #[inline]
442    fn add(self, rhs: i64) -> CBig {
443        add_scalar_i64(&self, rhs)
444    }
445}
446
447impl Add<i64> for &CBig {
448    type Output = CBig;
449    #[inline]
450    fn add(self, rhs: i64) -> CBig {
451        add_scalar_i64(self, rhs)
452    }
453}
454
455impl AddAssign<i64> for CBig {
456    #[inline]
457    fn add_assign(&mut self, rhs: i64) {
458        *self = add_scalar_i64(&*self, rhs);
459    }
460}
461
462// ---------------------------------------------------------------------------
463// CBig op i64 — Sub
464// ---------------------------------------------------------------------------
465
466impl Sub<i64> for CBig {
467    type Output = CBig;
468    #[inline]
469    fn sub(self, rhs: i64) -> CBig {
470        sub_scalar_i64(&self, rhs)
471    }
472}
473
474impl Sub<i64> for &CBig {
475    type Output = CBig;
476    #[inline]
477    fn sub(self, rhs: i64) -> CBig {
478        sub_scalar_i64(self, rhs)
479    }
480}
481
482impl SubAssign<i64> for CBig {
483    #[inline]
484    fn sub_assign(&mut self, rhs: i64) {
485        *self = sub_scalar_i64(&*self, rhs);
486    }
487}
488
489// ---------------------------------------------------------------------------
490// i64 op CBig — reversed Sub (n - z)
491// ---------------------------------------------------------------------------
492
493impl Sub<CBig> for i64 {
494    type Output = CBig;
495    #[inline]
496    fn sub(self, rhs: CBig) -> CBig {
497        rsub_scalar_i64(self, &rhs)
498    }
499}
500
501impl Sub<&CBig> for i64 {
502    type Output = CBig;
503    #[inline]
504    fn sub(self, rhs: &CBig) -> CBig {
505        rsub_scalar_i64(self, rhs)
506    }
507}
508
509// ---------------------------------------------------------------------------
510// CBig op i64 — Mul
511// ---------------------------------------------------------------------------
512
513impl Mul<i64> for CBig {
514    type Output = CBig;
515    #[inline]
516    fn mul(self, rhs: i64) -> CBig {
517        mul_scalar_i64(&self, rhs)
518    }
519}
520
521impl Mul<i64> for &CBig {
522    type Output = CBig;
523    #[inline]
524    fn mul(self, rhs: i64) -> CBig {
525        mul_scalar_i64(self, rhs)
526    }
527}
528
529impl MulAssign<i64> for CBig {
530    #[inline]
531    fn mul_assign(&mut self, rhs: i64) {
532        *self = mul_scalar_i64(&*self, rhs);
533    }
534}
535
536// ---------------------------------------------------------------------------
537// CBig op i64 — Div
538// ---------------------------------------------------------------------------
539
540impl Div<i64> for CBig {
541    type Output = CBig;
542    #[inline]
543    fn div(self, rhs: i64) -> CBig {
544        div_scalar_i64(&self, rhs)
545    }
546}
547
548impl Div<i64> for &CBig {
549    type Output = CBig;
550    #[inline]
551    fn div(self, rhs: i64) -> CBig {
552        div_scalar_i64(self, rhs)
553    }
554}
555
556impl DivAssign<i64> for CBig {
557    #[inline]
558    fn div_assign(&mut self, rhs: i64) {
559        *self = div_scalar_i64(&*self, rhs);
560    }
561}
562
563// ---------------------------------------------------------------------------
564// Public no-panic division
565// ---------------------------------------------------------------------------
566
567impl CBig {
568    /// Divides `self` by `rhs` without panicking.
569    ///
570    /// Returns [`OxiNumError::DivByZero`] when `rhs` is zero; otherwise yields
571    /// `self / rhs` computed as `(self · conj(rhs)) / |rhs|²`.
572    ///
573    /// This is the panic-free counterpart of the [`Div`] operator (which
574    /// panics on a zero divisor, matching the rest of the workspace) and is
575    /// the entry point sibling routines such as complex `tan`/`tanh` use.
576    ///
577    /// # Examples
578    ///
579    /// ```
580    /// use oxinum_complex::CBig;
581    ///
582    /// let a = CBig::from_f64(1.0, 1.0).expect("finite parts");
583    /// let q = a.checked_div(&a).expect("non-zero divisor");
584    /// assert_eq!(q.re().to_string(), "1");
585    /// assert_eq!(q.im().to_string(), "0");
586    ///
587    /// assert!(a.checked_div(&CBig::zero()).is_err());
588    /// ```
589    pub fn checked_div(&self, rhs: &CBig) -> OxiNumResult<CBig> {
590        div_core(self, rhs)
591    }
592}
593
594// ---------------------------------------------------------------------------
595// Macros — wire the four owned/borrowed variants and the *Assign traits to the
596// *_core helpers.
597// ---------------------------------------------------------------------------
598
599macro_rules! impl_binop {
600    ($Trait:ident, $method:ident, $core:ident) => {
601        impl $Trait<&CBig> for &CBig {
602            type Output = CBig;
603            #[inline]
604            fn $method(self, rhs: &CBig) -> CBig {
605                $core(self, rhs)
606            }
607        }
608
609        impl $Trait<CBig> for CBig {
610            type Output = CBig;
611            #[inline]
612            fn $method(self, rhs: CBig) -> CBig {
613                $core(&self, &rhs)
614            }
615        }
616
617        impl $Trait<&CBig> for CBig {
618            type Output = CBig;
619            #[inline]
620            fn $method(self, rhs: &CBig) -> CBig {
621                $core(&self, rhs)
622            }
623        }
624
625        impl $Trait<CBig> for &CBig {
626            type Output = CBig;
627            #[inline]
628            fn $method(self, rhs: CBig) -> CBig {
629                $core(self, &rhs)
630            }
631        }
632    };
633}
634
635macro_rules! impl_assign {
636    ($Trait:ident, $method:ident, $core:ident) => {
637        impl $Trait<&CBig> for CBig {
638            #[inline]
639            fn $method(&mut self, rhs: &CBig) {
640                *self = $core(&*self, rhs);
641            }
642        }
643
644        impl $Trait<CBig> for CBig {
645            #[inline]
646            fn $method(&mut self, rhs: CBig) {
647                *self = $core(&*self, &rhs);
648            }
649        }
650    };
651}
652
653impl_binop!(Add, add, add_core);
654impl_binop!(Sub, sub, sub_core);
655impl_binop!(Mul, mul, mul_core);
656impl_binop!(Div, div, div_op_core);
657
658impl_assign!(AddAssign, add_assign, add_core);
659impl_assign!(SubAssign, sub_assign, sub_core);
660impl_assign!(MulAssign, mul_assign, mul_core);
661impl_assign!(DivAssign, div_assign, div_op_core);
662
663// ---------------------------------------------------------------------------
664// Neg
665// ---------------------------------------------------------------------------
666
667impl Neg for CBig {
668    type Output = CBig;
669    #[inline]
670    fn neg(self) -> CBig {
671        CBig {
672            re: -self.re,
673            im: -self.im,
674        }
675    }
676}
677
678impl Neg for &CBig {
679    type Output = CBig;
680    #[inline]
681    fn neg(self) -> CBig {
682        CBig {
683            re: -&self.re,
684            im: -&self.im,
685        }
686    }
687}
688
689// ---------------------------------------------------------------------------
690// PartialEq (component-wise; no Eq — see crate-level docs)
691// ---------------------------------------------------------------------------
692
693impl PartialEq for CBig {
694    #[inline]
695    fn eq(&self, other: &Self) -> bool {
696        self.re == other.re && self.im == other.im
697    }
698}
699
700// ---------------------------------------------------------------------------
701// Default
702// ---------------------------------------------------------------------------
703
704impl Default for CBig {
705    #[inline]
706    fn default() -> Self {
707        CBig::zero()
708    }
709}
710
711// ---------------------------------------------------------------------------
712// Unit tests
713// ---------------------------------------------------------------------------
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    /// Build a `CBig` from two small integer-valued `f64`s.
720    fn c(re: f64, im: f64) -> CBig {
721        CBig::from_f64(re, im).expect("finite parts")
722    }
723
724    /// Assert that `z` equals `re + im·i` via exact decimal string compare.
725    fn assert_parts(z: &CBig, re: &str, im: &str) {
726        assert_eq!(z.re().to_string(), re, "real part mismatch");
727        assert_eq!(z.im().to_string(), im, "imag part mismatch");
728    }
729
730    #[test]
731    fn add_component_wise() {
732        // (1 + 2i) + (3 + 4i) = 4 + 6i
733        let sum = c(1.0, 2.0) + c(3.0, 4.0);
734        assert_parts(&sum, "4", "6");
735    }
736
737    #[test]
738    fn sub_component_wise() {
739        // (1 + 2i) − (3 + 4i) = -2 − 2i
740        let diff = c(1.0, 2.0) - c(3.0, 4.0);
741        assert_parts(&diff, "-2", "-2");
742    }
743
744    #[test]
745    fn mul_hand_computed() {
746        // (1 + 2i)(3 + 4i) = (3 − 8) + (4 + 6)i = -5 + 10i
747        let prod = c(1.0, 2.0) * c(3.0, 4.0);
748        assert_parts(&prod, "-5", "10");
749    }
750
751    #[test]
752    fn mul_i_squared_is_minus_one() {
753        // i · i = -1
754        let prod = CBig::i() * CBig::i();
755        assert_parts(&prod, "-1", "0");
756    }
757
758    #[test]
759    fn mul_one_plus_i_squared_is_two_i() {
760        // (1 + i)² = 1 + 2i + i² = 2i
761        let z = c(1.0, 1.0);
762        let sq = &z * &z;
763        assert_parts(&sq, "0", "2");
764    }
765
766    #[test]
767    fn div_self_is_one() {
768        // (1 + i) / (1 + i) = 1
769        let z = c(1.0, 1.0);
770        let q = &z / &z;
771        assert_parts(&q, "1", "0");
772    }
773
774    #[test]
775    fn div_general() {
776        // (3 + 4i) / (1 + 2i):
777        //   conj denom math → num = (3·1 + 4·2) + (4·1 − 3·2)i = 11 − 2i
778        //   |1 + 2i|² = 5  → (11/5) + (-2/5)i = 2.2 − 0.4i
779        let q = c(3.0, 4.0) / c(1.0, 2.0);
780        assert_parts(&q, "2.2", "-0.4");
781    }
782
783    #[test]
784    fn checked_div_self_is_one() {
785        let z = c(1.0, 1.0);
786        let q = z.checked_div(&z).expect("non-zero divisor");
787        assert_parts(&q, "1", "0");
788    }
789
790    #[test]
791    fn checked_div_by_zero_is_err() {
792        let z = c(1.0, 1.0);
793        // `CBig` intentionally has no `Debug` (provided, if at all, by the
794        // sibling `fmt` module), so match on the error variant directly rather
795        // than unwrapping the `Ok` payload.
796        assert!(matches!(
797            z.checked_div(&CBig::zero()),
798            Err(OxiNumError::DivByZero)
799        ));
800    }
801
802    #[test]
803    #[should_panic]
804    fn div_operator_by_zero_panics() {
805        let z = c(1.0, 1.0);
806        let _ = z / CBig::zero();
807    }
808
809    #[test]
810    fn norm_sqr_exact() {
811        // |3 + 4i|² = 25 (exact, integer result)
812        assert_eq!(c(3.0, 4.0).norm_sqr().to_string(), "25");
813    }
814
815    #[test]
816    fn default_is_zero() {
817        let d = CBig::default();
818        assert!(d.is_zero());
819        // Exercise `PartialEq` without relying on `Debug` (asserted via the
820        // boolean form because `CBig` has no `Debug` in this module).
821        assert!(d == CBig::zero());
822    }
823
824    #[test]
825    fn partial_eq_component_wise() {
826        assert!(c(1.5, -2.0) == c(1.5, -2.0));
827        assert!(c(1.5, -2.0) != c(1.5, 2.0));
828        assert!(c(1.5, -2.0) != c(-1.5, -2.0));
829    }
830
831    #[test]
832    fn neg_owned_and_borrowed() {
833        let z = c(2.0, -3.0);
834        let n_ref = -&z;
835        assert_parts(&n_ref, "-2", "3");
836        let n_owned = -z;
837        assert_parts(&n_owned, "-2", "3");
838    }
839
840    #[test]
841    fn add_assign_accumulates() {
842        let mut a = c(1.0, 2.0);
843        a += c(3.0, 4.0);
844        assert_parts(&a, "4", "6");
845        a += &c(-4.0, -6.0);
846        assert!(a.is_zero());
847    }
848
849    #[test]
850    fn mul_assign_updates_in_place() {
851        // (1 + i) *= (1 + i) → 2i
852        let mut a = c(1.0, 1.0);
853        a *= &c(1.0, 1.0);
854        assert_parts(&a, "0", "2");
855    }
856
857    #[test]
858    fn sub_and_div_assign() {
859        let mut a = c(5.0, 5.0);
860        a -= c(2.0, 1.0);
861        assert_parts(&a, "3", "4");
862        // (3 + 4i) /= (3 + 4i) → 1
863        a /= c(3.0, 4.0);
864        assert_parts(&a, "1", "0");
865    }
866
867    #[test]
868    fn ownership_variants_consistent() {
869        // All four flavours of Add agree. Compared via `PartialEq`'s boolean
870        // form so the test needs no `Debug` impl for `CBig`.
871        let a = c(1.0, 2.0);
872        let b = c(3.0, 4.0);
873        let target = c(4.0, 6.0);
874        assert!(&a + &b == target);
875        assert!(a.clone() + b.clone() == target);
876        assert!(a.clone() + &b == target);
877        assert!(&a + b.clone() == target);
878    }
879
880    // -----------------------------------------------------------------------
881    // Scalar DBig ops
882    // -----------------------------------------------------------------------
883
884    #[test]
885    fn add_dbig_scalar() {
886        let z = CBig::from_f64(1.0, 2.0).expect("ok");
887        let d = DBig::from(3u32);
888        let r = z + &d;
889        assert_eq!(r.re().to_string(), "4");
890        assert_eq!(r.im().to_string(), "2");
891    }
892
893    #[test]
894    fn mul_dbig_scalar() {
895        let z = CBig::i(); // 0 + 1i
896        let d = DBig::from(3u32);
897        let r = &z * &d;
898        assert_eq!(r.re().to_string(), "0");
899        assert_eq!(r.im().to_string(), "3");
900    }
901
902    #[test]
903    fn div_dbig_scalar() {
904        let z = CBig::from_f64(6.0, 4.0).expect("ok");
905        let d = DBig::from(2u32);
906        let r = z / d;
907        assert_eq!(r.re().to_string(), "3");
908        assert_eq!(r.im().to_string(), "2");
909    }
910
911    #[test]
912    fn sub_reversed_dbig_minus_z() {
913        // 5 - (3 + 2i) = 2 - 2i
914        let z = CBig::from_f64(3.0, 2.0).expect("ok");
915        let d = DBig::from(5u32);
916        let r = d - &z;
917        assert_eq!(r.re().to_string(), "2");
918        assert_eq!(r.im().to_string(), "-2");
919    }
920
921    #[test]
922    fn dbig_scalar_assign_ops() {
923        // AddAssign
924        let mut z = CBig::from_f64(1.0, 2.0).expect("ok");
925        z += DBig::from(3u32);
926        assert_eq!(z.re().to_string(), "4");
927        assert_eq!(z.im().to_string(), "2");
928
929        // SubAssign
930        z -= &DBig::from(1u32);
931        assert_eq!(z.re().to_string(), "3");
932
933        // MulAssign
934        z *= DBig::from(2u32);
935        assert_eq!(z.re().to_string(), "6");
936        assert_eq!(z.im().to_string(), "4");
937
938        // DivAssign
939        z /= &DBig::from(2u32);
940        assert_eq!(z.re().to_string(), "3");
941        assert_eq!(z.im().to_string(), "2");
942    }
943
944    // -----------------------------------------------------------------------
945    // Scalar i64 ops
946    // -----------------------------------------------------------------------
947
948    #[test]
949    fn mul_i64_scalar() {
950        let z = CBig::i(); // 0 + 1i
951        let r = z * 5i64;
952        assert_eq!(r.re().to_string(), "0");
953        assert_eq!(r.im().to_string(), "5");
954    }
955
956    #[test]
957    fn add_i64_scalar_assign() {
958        let mut z = CBig::from_f64(1.0, 0.0).expect("ok");
959        z += 4i64;
960        assert_eq!(z.re().to_string(), "5");
961        assert_eq!(z.im().to_string(), "0");
962    }
963
964    #[test]
965    fn i64_scalar_product_is_exact() {
966        // (3+4i) * 5 = 15+20i, exact
967        let z: CBig = (3i64, 4i64).into();
968        let r = z * 5i64;
969        assert_eq!(r.re().to_string(), "15");
970        assert_eq!(r.im().to_string(), "20");
971    }
972
973    #[test]
974    fn sub_i64_scalar() {
975        // (5 + 3i) - 2 = 3 + 3i
976        let z = CBig::from_f64(5.0, 3.0).expect("ok");
977        let r = z - 2i64;
978        assert_eq!(r.re().to_string(), "3");
979        assert_eq!(r.im().to_string(), "3");
980    }
981
982    #[test]
983    fn sub_reversed_i64_minus_z() {
984        // 5 - (3 + 2i) = 2 - 2i
985        let z = CBig::from_f64(3.0, 2.0).expect("ok");
986        let r = 5i64 - &z;
987        assert_eq!(r.re().to_string(), "2");
988        assert_eq!(r.im().to_string(), "-2");
989    }
990
991    #[test]
992    fn div_i64_scalar() {
993        // (6 + 4i) / 2 = 3 + 2i
994        let z = CBig::from_f64(6.0, 4.0).expect("ok");
995        let r = z / 2i64;
996        assert_eq!(r.re().to_string(), "3");
997        assert_eq!(r.im().to_string(), "2");
998    }
999
1000    #[test]
1001    fn i64_scalar_ref_ops_consistent() {
1002        // &z * n == z.clone() * n
1003        let z = CBig::from_f64(2.0, 3.0).expect("ok");
1004        let r1 = &z * 4i64;
1005        let r2 = z.clone() * 4i64;
1006        assert!(r1 == r2);
1007    }
1008}