Skip to main content

oxinum_complex/native/
complex_ops.rs

1//! Arithmetic operator implementations for native [`BigComplex`].
2//!
3//! Every infallible binary operator — `Add`, `Sub`, `Mul` — provides all four
4//! ownership variants (`&T op &T`, `T op T`, `T op &T`, `&T op T`), each
5//! producing an owned [`BigComplex`]. The matching `*Assign` traits are wired
6//! up for both owned and borrowed right-hand sides, and [`Neg`] is provided for
7//! owned and borrowed receivers. Component-wise [`PartialEq`] is also provided.
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//! # Why there is no `Div` operator and no `Default`
22//!
23//! * **No `Div` operator.** Native complex division is intrinsically
24//!   precision-and-rounding aware (it must divide by the real `norm_sqr`), so
25//!   it cannot be expressed through the precision-free `core::ops::Div`
26//!   signature without smuggling in a default precision. Division is therefore
27//!   exposed only as the explicit [`BigComplex::checked_div`], which takes
28//!   `(prec, mode)` and returns [`OxiNumResult`], surfacing
29//!   [`OxiNumError::DivByZero`] for a zero divisor rather than panicking.
30//! * **No `Default`.** A `BigComplex` value is meaningless without a chosen
31//!   working precision, and there is no single precision the library can pick
32//!   that is correct for every caller. Rather than bake in an arbitrary
33//!   constant, `Default` is intentionally *not* implemented; callers construct
34//!   the additive identity explicitly via [`BigComplex::zero(prec)`].
35//!
36//! `Add`/`Sub`/`Neg` are infallible because the underlying [`BigFloat`]
37//! operators are infallible; `Mul` routes through the private [`mul_core`],
38//! which combines the four cross-products with banker's rounding at each
39//! component's own precision.
40
41use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
42
43use oxinum_core::OxiNumResult;
44use oxinum_float::native::{BigFloat, RoundingMode};
45
46use super::BigComplex;
47
48impl BigComplex {
49    /// The complex product `self · rhs` via the four real cross-products.
50    ///
51    /// `(a + b·i)(c + d·i) = (a·c − b·d) + (a·d + b·c)·i`.
52    ///
53    /// Each component is formed with banker's rounding ([`RoundingMode::HalfEven`])
54    /// using the infallible reference multiply/add on [`BigFloat`].
55    pub(crate) fn mul_core(&self, rhs: &BigComplex) -> BigComplex {
56        let a = &self.re;
57        let b = &self.im;
58        let c = &rhs.re;
59        let d = &rhs.im;
60
61        // re = a·c − b·d
62        let ac = a * c;
63        let bd = b * d;
64        let re = &ac - &bd;
65
66        // im = a·d + b·c
67        let ad = a * d;
68        let bc = b * c;
69        let im = &ad + &bc;
70
71        BigComplex { re, im }
72    }
73
74    /// The complex quotient `self / rhs` at `prec` bits with the given rounding
75    /// mode, computed as `(self · conj(rhs)) / |rhs|²`.
76    ///
77    /// Returns [`OxiNumError::DivByZero`](oxinum_core::OxiNumError::DivByZero)
78    /// when `rhs` is the (component-wise) zero, detected via
79    /// [`BigComplex::is_zero`] / a zero `norm_sqr`. This is the no-panic core
80    /// shared by [`BigComplex::checked_div`].
81    pub(crate) fn div_core(
82        &self,
83        rhs: &BigComplex,
84        prec: u32,
85        mode: RoundingMode,
86    ) -> OxiNumResult<BigComplex> {
87        // Zero-divisor guard: surface DivByZero rather than panicking. The
88        // real div_ref below would itself return DivByZero, but checking the
89        // norm first gives a single, unambiguous error site.
90        if rhs.is_zero() {
91            return Err(oxinum_core::OxiNumError::DivByZero);
92        }
93
94        let guard = prec.saturating_add(10);
95
96        let a = &self.re;
97        let b = &self.im;
98        let c = &rhs.re;
99        let d = &rhs.im;
100
101        // denominator = c² + d² (real, strictly positive here).
102        let denom = rhs.norm_sqr();
103
104        // numerator real part: a·c + b·d
105        let ac = a * c;
106        let bd = b * d;
107        let num_re = &ac + &bd;
108
109        // numerator imag part: b·c − a·d
110        let bc = b * c;
111        let ad = a * d;
112        let num_im = &bc - &ad;
113
114        // `guard` documents the headroom carried by `norm_sqr` / the
115        // cross-products (all formed at the operands' own precision, which the
116        // callers set >= prec); the final components are delivered at `prec`.
117        let _ = guard;
118        let re = num_re
119            .div_ref_with_mode(&denom, mode)?
120            .with_precision(prec, mode);
121        let im = num_im
122            .div_ref_with_mode(&denom, mode)?
123            .with_precision(prec, mode);
124
125        Ok(BigComplex { re, im })
126    }
127
128    /// Checked complex division `self / rhs` at `prec` bits.
129    ///
130    /// This is the public, no-panic division entry point for native complex
131    /// values (there is deliberately no `core::ops::Div` impl — see the module
132    /// docs). The result components are rounded to `prec` bits with `mode`.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`OxiNumError::DivByZero`](oxinum_core::OxiNumError::DivByZero)
137    /// if `rhs` is zero.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use oxinum_complex::native::{BigComplex, RoundingMode};
143    /// // (2 + 0i) / (1 + i) = 1 − i.
144    /// let num = BigComplex::from_f64(2.0, 0.0, 80).expect("finite");
145    /// let den = BigComplex::from_f64(1.0, 1.0, 80).expect("finite");
146    /// let q = num
147    ///     .checked_div(&den, 80, RoundingMode::HalfEven)
148    ///     .expect("non-zero divisor");
149    /// assert!((q.re().to_f64() - 1.0).abs() < 1e-12);
150    /// assert!((q.im().to_f64() + 1.0).abs() < 1e-12);
151    /// ```
152    pub fn checked_div(
153        &self,
154        rhs: &BigComplex,
155        prec: u32,
156        mode: RoundingMode,
157    ) -> OxiNumResult<BigComplex> {
158        self.div_core(rhs, prec, mode)
159    }
160}
161
162// ---------------------------------------------------------------------------
163// Add
164// ---------------------------------------------------------------------------
165
166impl Add<&BigComplex> for &BigComplex {
167    type Output = BigComplex;
168    #[inline]
169    fn add(self, rhs: &BigComplex) -> BigComplex {
170        BigComplex {
171            re: &self.re + &rhs.re,
172            im: &self.im + &rhs.im,
173        }
174    }
175}
176
177impl Add<BigComplex> for BigComplex {
178    type Output = BigComplex;
179    #[inline]
180    fn add(self, rhs: BigComplex) -> BigComplex {
181        (&self).add(&rhs)
182    }
183}
184
185impl Add<&BigComplex> for BigComplex {
186    type Output = BigComplex;
187    #[inline]
188    fn add(self, rhs: &BigComplex) -> BigComplex {
189        (&self).add(rhs)
190    }
191}
192
193impl Add<BigComplex> for &BigComplex {
194    type Output = BigComplex;
195    #[inline]
196    fn add(self, rhs: BigComplex) -> BigComplex {
197        self.add(&rhs)
198    }
199}
200
201impl AddAssign<&BigComplex> for BigComplex {
202    #[inline]
203    fn add_assign(&mut self, rhs: &BigComplex) {
204        *self = (&*self).add(rhs);
205    }
206}
207
208impl AddAssign<BigComplex> for BigComplex {
209    #[inline]
210    fn add_assign(&mut self, rhs: BigComplex) {
211        *self = (&*self).add(&rhs);
212    }
213}
214
215// ---------------------------------------------------------------------------
216// Sub
217// ---------------------------------------------------------------------------
218
219impl Sub<&BigComplex> for &BigComplex {
220    type Output = BigComplex;
221    #[inline]
222    fn sub(self, rhs: &BigComplex) -> BigComplex {
223        BigComplex {
224            re: &self.re - &rhs.re,
225            im: &self.im - &rhs.im,
226        }
227    }
228}
229
230impl Sub<BigComplex> for BigComplex {
231    type Output = BigComplex;
232    #[inline]
233    fn sub(self, rhs: BigComplex) -> BigComplex {
234        (&self).sub(&rhs)
235    }
236}
237
238impl Sub<&BigComplex> for BigComplex {
239    type Output = BigComplex;
240    #[inline]
241    fn sub(self, rhs: &BigComplex) -> BigComplex {
242        (&self).sub(rhs)
243    }
244}
245
246impl Sub<BigComplex> for &BigComplex {
247    type Output = BigComplex;
248    #[inline]
249    fn sub(self, rhs: BigComplex) -> BigComplex {
250        self.sub(&rhs)
251    }
252}
253
254impl SubAssign<&BigComplex> for BigComplex {
255    #[inline]
256    fn sub_assign(&mut self, rhs: &BigComplex) {
257        *self = (&*self).sub(rhs);
258    }
259}
260
261impl SubAssign<BigComplex> for BigComplex {
262    #[inline]
263    fn sub_assign(&mut self, rhs: BigComplex) {
264        *self = (&*self).sub(&rhs);
265    }
266}
267
268// ---------------------------------------------------------------------------
269// Mul
270// ---------------------------------------------------------------------------
271
272impl Mul<&BigComplex> for &BigComplex {
273    type Output = BigComplex;
274    #[inline]
275    fn mul(self, rhs: &BigComplex) -> BigComplex {
276        self.mul_core(rhs)
277    }
278}
279
280impl Mul<BigComplex> for BigComplex {
281    type Output = BigComplex;
282    #[inline]
283    fn mul(self, rhs: BigComplex) -> BigComplex {
284        self.mul_core(&rhs)
285    }
286}
287
288impl Mul<&BigComplex> for BigComplex {
289    type Output = BigComplex;
290    #[inline]
291    fn mul(self, rhs: &BigComplex) -> BigComplex {
292        self.mul_core(rhs)
293    }
294}
295
296impl Mul<BigComplex> for &BigComplex {
297    type Output = BigComplex;
298    #[inline]
299    fn mul(self, rhs: BigComplex) -> BigComplex {
300        self.mul_core(&rhs)
301    }
302}
303
304impl MulAssign<&BigComplex> for BigComplex {
305    #[inline]
306    fn mul_assign(&mut self, rhs: &BigComplex) {
307        *self = self.mul_core(rhs);
308    }
309}
310
311impl MulAssign<BigComplex> for BigComplex {
312    #[inline]
313    fn mul_assign(&mut self, rhs: BigComplex) {
314        *self = self.mul_core(&rhs);
315    }
316}
317
318// ---------------------------------------------------------------------------
319// Neg
320// ---------------------------------------------------------------------------
321
322impl Neg for &BigComplex {
323    type Output = BigComplex;
324    #[inline]
325    fn neg(self) -> BigComplex {
326        BigComplex {
327            re: -&self.re,
328            im: -&self.im,
329        }
330    }
331}
332
333impl Neg for BigComplex {
334    type Output = BigComplex;
335    #[inline]
336    fn neg(self) -> BigComplex {
337        (&self).neg()
338    }
339}
340
341// ---------------------------------------------------------------------------
342// PartialEq — component-wise (no Eq: BigFloat is not Eq because of NaN).
343// ---------------------------------------------------------------------------
344
345impl PartialEq for BigComplex {
346    #[inline]
347    fn eq(&self, other: &Self) -> bool {
348        self.re == other.re && self.im == other.im
349    }
350}
351
352// ---------------------------------------------------------------------------
353// Scalar helpers for BigComplex + {BigFloat, i64}
354// ---------------------------------------------------------------------------
355
356/// `z + d = (re + d, im)`.
357#[inline]
358fn add_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
359    BigComplex {
360        re: &z.re + d,
361        im: z.im.clone(),
362    }
363}
364
365/// `z - d = (re - d, im)`.
366#[inline]
367fn sub_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
368    BigComplex {
369        re: &z.re - d,
370        im: z.im.clone(),
371    }
372}
373
374/// `d - z = (d - re, -im)`.
375#[inline]
376fn rsub_scalar_bf(d: &BigFloat, z: &BigComplex) -> BigComplex {
377    BigComplex {
378        re: d - &z.re,
379        im: -&z.im,
380    }
381}
382
383/// `z * d = (re*d, im*d)`.
384#[inline]
385fn mul_scalar_bf(z: &BigComplex, d: &BigFloat) -> BigComplex {
386    BigComplex {
387        re: &z.re * d,
388        im: &z.im * d,
389    }
390}
391
392/// Convert an `i64` to a `BigFloat` at precision `prec`.
393#[inline]
394fn bf_from_i64_at(n: i64, prec: u32) -> BigFloat {
395    BigFloat::from_i64(n, prec, RoundingMode::HalfEven)
396}
397
398/// Scalar `i64` cores — delegate to the `BigFloat` variants after converting.
399#[inline]
400fn add_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
401    add_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
402}
403#[inline]
404fn sub_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
405    sub_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
406}
407#[inline]
408fn rsub_scalar_i64(n: i64, z: &BigComplex) -> BigComplex {
409    rsub_scalar_bf(&bf_from_i64_at(n, z.re.precision()), z)
410}
411#[inline]
412fn mul_scalar_i64(z: &BigComplex, n: i64) -> BigComplex {
413    mul_scalar_bf(z, &bf_from_i64_at(n, z.re.precision()))
414}
415
416// ---------------------------------------------------------------------------
417// BigComplex op BigFloat — Add
418// ---------------------------------------------------------------------------
419
420impl Add<BigFloat> for BigComplex {
421    type Output = BigComplex;
422    #[inline]
423    fn add(self, rhs: BigFloat) -> BigComplex {
424        add_scalar_bf(&self, &rhs)
425    }
426}
427
428impl Add<&BigFloat> for BigComplex {
429    type Output = BigComplex;
430    #[inline]
431    fn add(self, rhs: &BigFloat) -> BigComplex {
432        add_scalar_bf(&self, rhs)
433    }
434}
435
436impl Add<BigFloat> for &BigComplex {
437    type Output = BigComplex;
438    #[inline]
439    fn add(self, rhs: BigFloat) -> BigComplex {
440        add_scalar_bf(self, &rhs)
441    }
442}
443
444impl Add<&BigFloat> for &BigComplex {
445    type Output = BigComplex;
446    #[inline]
447    fn add(self, rhs: &BigFloat) -> BigComplex {
448        add_scalar_bf(self, rhs)
449    }
450}
451
452impl AddAssign<BigFloat> for BigComplex {
453    #[inline]
454    fn add_assign(&mut self, rhs: BigFloat) {
455        *self = add_scalar_bf(self, &rhs);
456    }
457}
458
459impl AddAssign<&BigFloat> for BigComplex {
460    #[inline]
461    fn add_assign(&mut self, rhs: &BigFloat) {
462        *self = add_scalar_bf(self, rhs);
463    }
464}
465
466// ---------------------------------------------------------------------------
467// BigComplex op BigFloat — Sub
468// ---------------------------------------------------------------------------
469
470impl Sub<BigFloat> for BigComplex {
471    type Output = BigComplex;
472    #[inline]
473    fn sub(self, rhs: BigFloat) -> BigComplex {
474        sub_scalar_bf(&self, &rhs)
475    }
476}
477
478impl Sub<&BigFloat> for BigComplex {
479    type Output = BigComplex;
480    #[inline]
481    fn sub(self, rhs: &BigFloat) -> BigComplex {
482        sub_scalar_bf(&self, rhs)
483    }
484}
485
486impl Sub<BigFloat> for &BigComplex {
487    type Output = BigComplex;
488    #[inline]
489    fn sub(self, rhs: BigFloat) -> BigComplex {
490        sub_scalar_bf(self, &rhs)
491    }
492}
493
494impl Sub<&BigFloat> for &BigComplex {
495    type Output = BigComplex;
496    #[inline]
497    fn sub(self, rhs: &BigFloat) -> BigComplex {
498        sub_scalar_bf(self, rhs)
499    }
500}
501
502impl SubAssign<BigFloat> for BigComplex {
503    #[inline]
504    fn sub_assign(&mut self, rhs: BigFloat) {
505        *self = sub_scalar_bf(self, &rhs);
506    }
507}
508
509impl SubAssign<&BigFloat> for BigComplex {
510    #[inline]
511    fn sub_assign(&mut self, rhs: &BigFloat) {
512        *self = sub_scalar_bf(self, rhs);
513    }
514}
515
516// ---------------------------------------------------------------------------
517// BigFloat op BigComplex — reversed Sub (d - z)
518// ---------------------------------------------------------------------------
519
520impl Sub<BigComplex> for BigFloat {
521    type Output = BigComplex;
522    #[inline]
523    fn sub(self, rhs: BigComplex) -> BigComplex {
524        rsub_scalar_bf(&self, &rhs)
525    }
526}
527
528impl Sub<&BigComplex> for BigFloat {
529    type Output = BigComplex;
530    #[inline]
531    fn sub(self, rhs: &BigComplex) -> BigComplex {
532        rsub_scalar_bf(&self, rhs)
533    }
534}
535
536impl Sub<BigComplex> for &BigFloat {
537    type Output = BigComplex;
538    #[inline]
539    fn sub(self, rhs: BigComplex) -> BigComplex {
540        rsub_scalar_bf(self, &rhs)
541    }
542}
543
544impl Sub<&BigComplex> for &BigFloat {
545    type Output = BigComplex;
546    #[inline]
547    fn sub(self, rhs: &BigComplex) -> BigComplex {
548        rsub_scalar_bf(self, rhs)
549    }
550}
551
552// ---------------------------------------------------------------------------
553// BigComplex op BigFloat — Mul
554// ---------------------------------------------------------------------------
555
556impl Mul<BigFloat> for BigComplex {
557    type Output = BigComplex;
558    #[inline]
559    fn mul(self, rhs: BigFloat) -> BigComplex {
560        mul_scalar_bf(&self, &rhs)
561    }
562}
563
564impl Mul<&BigFloat> for BigComplex {
565    type Output = BigComplex;
566    #[inline]
567    fn mul(self, rhs: &BigFloat) -> BigComplex {
568        mul_scalar_bf(&self, rhs)
569    }
570}
571
572impl Mul<BigFloat> for &BigComplex {
573    type Output = BigComplex;
574    #[inline]
575    fn mul(self, rhs: BigFloat) -> BigComplex {
576        mul_scalar_bf(self, &rhs)
577    }
578}
579
580impl Mul<&BigFloat> for &BigComplex {
581    type Output = BigComplex;
582    #[inline]
583    fn mul(self, rhs: &BigFloat) -> BigComplex {
584        mul_scalar_bf(self, rhs)
585    }
586}
587
588impl MulAssign<BigFloat> for BigComplex {
589    #[inline]
590    fn mul_assign(&mut self, rhs: BigFloat) {
591        *self = mul_scalar_bf(self, &rhs);
592    }
593}
594
595impl MulAssign<&BigFloat> for BigComplex {
596    #[inline]
597    fn mul_assign(&mut self, rhs: &BigFloat) {
598        *self = mul_scalar_bf(self, rhs);
599    }
600}
601
602// ---------------------------------------------------------------------------
603// BigComplex op i64 — Add
604// ---------------------------------------------------------------------------
605
606impl Add<i64> for BigComplex {
607    type Output = BigComplex;
608    #[inline]
609    fn add(self, rhs: i64) -> BigComplex {
610        add_scalar_i64(&self, rhs)
611    }
612}
613
614impl Add<i64> for &BigComplex {
615    type Output = BigComplex;
616    #[inline]
617    fn add(self, rhs: i64) -> BigComplex {
618        add_scalar_i64(self, rhs)
619    }
620}
621
622impl AddAssign<i64> for BigComplex {
623    #[inline]
624    fn add_assign(&mut self, rhs: i64) {
625        *self = add_scalar_i64(self, rhs);
626    }
627}
628
629// ---------------------------------------------------------------------------
630// BigComplex op i64 — Sub
631// ---------------------------------------------------------------------------
632
633impl Sub<i64> for BigComplex {
634    type Output = BigComplex;
635    #[inline]
636    fn sub(self, rhs: i64) -> BigComplex {
637        sub_scalar_i64(&self, rhs)
638    }
639}
640
641impl Sub<i64> for &BigComplex {
642    type Output = BigComplex;
643    #[inline]
644    fn sub(self, rhs: i64) -> BigComplex {
645        sub_scalar_i64(self, rhs)
646    }
647}
648
649impl SubAssign<i64> for BigComplex {
650    #[inline]
651    fn sub_assign(&mut self, rhs: i64) {
652        *self = sub_scalar_i64(self, rhs);
653    }
654}
655
656// ---------------------------------------------------------------------------
657// i64 op BigComplex — reversed Sub (n - z)
658// ---------------------------------------------------------------------------
659
660impl Sub<BigComplex> for i64 {
661    type Output = BigComplex;
662    #[inline]
663    fn sub(self, rhs: BigComplex) -> BigComplex {
664        rsub_scalar_i64(self, &rhs)
665    }
666}
667
668impl Sub<&BigComplex> for i64 {
669    type Output = BigComplex;
670    #[inline]
671    fn sub(self, rhs: &BigComplex) -> BigComplex {
672        rsub_scalar_i64(self, rhs)
673    }
674}
675
676// ---------------------------------------------------------------------------
677// BigComplex op i64 — Mul
678// ---------------------------------------------------------------------------
679
680impl Mul<i64> for BigComplex {
681    type Output = BigComplex;
682    #[inline]
683    fn mul(self, rhs: i64) -> BigComplex {
684        mul_scalar_i64(&self, rhs)
685    }
686}
687
688impl Mul<i64> for &BigComplex {
689    type Output = BigComplex;
690    #[inline]
691    fn mul(self, rhs: i64) -> BigComplex {
692        mul_scalar_i64(self, rhs)
693    }
694}
695
696impl MulAssign<i64> for BigComplex {
697    #[inline]
698    fn mul_assign(&mut self, rhs: i64) {
699        *self = mul_scalar_i64(self, rhs);
700    }
701}
702
703// ---------------------------------------------------------------------------
704// Tests
705// ---------------------------------------------------------------------------
706
707#[cfg(test)]
708mod tests {
709    use super::*;
710
711    const PREC: u32 = 80;
712    const MODE: RoundingMode = RoundingMode::HalfEven;
713
714    fn c(re: f64, im: f64) -> BigComplex {
715        BigComplex::from_f64(re, im, PREC).expect("finite parts")
716    }
717
718    #[test]
719    fn add_sub_componentwise() {
720        let z = &c(1.0, 2.0) + &c(3.0, -4.0);
721        assert!((z.re().to_f64() - 4.0).abs() < 1e-12);
722        assert!((z.im().to_f64() + 2.0).abs() < 1e-12);
723
724        let w = &c(1.0, 2.0) - &c(3.0, -4.0);
725        assert!((w.re().to_f64() + 2.0).abs() < 1e-12);
726        assert!((w.im().to_f64() - 6.0).abs() < 1e-12);
727    }
728
729    #[test]
730    fn mul_basic() {
731        // (1 + 2i)(3 + 4i) = -5 + 10i
732        let z = &c(1.0, 2.0) * &c(3.0, 4.0);
733        assert!(
734            (z.re().to_f64() + 5.0).abs() < 1e-12,
735            "re = {}",
736            z.re().to_f64()
737        );
738        assert!(
739            (z.im().to_f64() - 10.0).abs() < 1e-12,
740            "im = {}",
741            z.im().to_f64()
742        );
743    }
744
745    #[test]
746    fn mul_i_squared() {
747        // (1 + i)² = 2i
748        let one_plus_i = c(1.0, 1.0);
749        let z = &one_plus_i * &one_plus_i;
750        assert!(z.re().to_f64().abs() < 1e-12, "re = {}", z.re().to_f64());
751        assert!(
752            (z.im().to_f64() - 2.0).abs() < 1e-12,
753            "im = {}",
754            z.im().to_f64()
755        );
756    }
757
758    #[test]
759    fn checked_div_basic() {
760        // (2 + 0i) / (1 + i) = 1 − i
761        let q = c(2.0, 0.0)
762            .checked_div(&c(1.0, 1.0), PREC, MODE)
763            .expect("non-zero divisor");
764        assert!(
765            (q.re().to_f64() - 1.0).abs() < 1e-12,
766            "re = {}",
767            q.re().to_f64()
768        );
769        assert!(
770            (q.im().to_f64() + 1.0).abs() < 1e-12,
771            "im = {}",
772            q.im().to_f64()
773        );
774    }
775
776    #[test]
777    fn checked_div_general() {
778        // (1 + 2i)/(3 + 4i) = (11 + 2i)/25 = 0.44 + 0.08i
779        let q = c(1.0, 2.0)
780            .checked_div(&c(3.0, 4.0), PREC, MODE)
781            .expect("non-zero divisor");
782        assert!(
783            (q.re().to_f64() - 0.44).abs() < 1e-12,
784            "re = {}",
785            q.re().to_f64()
786        );
787        assert!(
788            (q.im().to_f64() - 0.08).abs() < 1e-12,
789            "im = {}",
790            q.im().to_f64()
791        );
792    }
793
794    #[test]
795    fn checked_div_by_zero_is_err() {
796        let q = c(1.0, 1.0).checked_div(&BigComplex::zero(PREC), PREC, MODE);
797        assert!(
798            matches!(q, Err(oxinum_core::OxiNumError::DivByZero)),
799            "expected DivByZero, got {q:?}"
800        );
801    }
802
803    #[test]
804    fn neg_and_assign_ops() {
805        let z = -&c(1.0, -2.0);
806        assert!((z.re().to_f64() + 1.0).abs() < 1e-12);
807        assert!((z.im().to_f64() - 2.0).abs() < 1e-12);
808
809        let mut a = c(1.0, 1.0);
810        a += &c(2.0, 3.0);
811        assert!((a.re().to_f64() - 3.0).abs() < 1e-12);
812        assert!((a.im().to_f64() - 4.0).abs() < 1e-12);
813
814        let mut b = c(5.0, 5.0);
815        b -= c(1.0, 2.0);
816        assert!((b.re().to_f64() - 4.0).abs() < 1e-12);
817        assert!((b.im().to_f64() - 3.0).abs() < 1e-12);
818
819        let mut m = c(1.0, 2.0);
820        m *= &c(3.0, 4.0);
821        assert!((m.re().to_f64() + 5.0).abs() < 1e-12);
822        assert!((m.im().to_f64() - 10.0).abs() < 1e-12);
823    }
824
825    #[test]
826    fn partial_eq_componentwise() {
827        assert_eq!(c(1.0, 2.0), c(1.0, 2.0));
828        assert_ne!(c(1.0, 2.0), c(1.0, 2.5));
829    }
830
831    // -----------------------------------------------------------------------
832    // Scalar BigFloat ops
833    // -----------------------------------------------------------------------
834
835    #[test]
836    fn add_bigfloat_scalar() {
837        let z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
838        let d = BigFloat::from_i64(3, PREC, MODE);
839        let r = &z + &d;
840        assert!((r.re().to_f64() - 4.0).abs() < 1e-12);
841        assert!((r.im().to_f64() - 2.0).abs() < 1e-12);
842    }
843
844    #[test]
845    fn sub_bigfloat_scalar() {
846        let z = BigComplex::from_f64(5.0, 3.0, PREC).expect("ok");
847        let d = BigFloat::from_i64(2, PREC, MODE);
848        let r = &z - &d;
849        assert!((r.re().to_f64() - 3.0).abs() < 1e-12);
850        assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
851    }
852
853    #[test]
854    fn mul_bigfloat_scalar() {
855        let z = BigComplex::from_f64(0.0, 1.0, PREC).expect("i");
856        let d = BigFloat::from_i64(3, PREC, MODE);
857        let r = z * d;
858        assert!(r.re().to_f64().abs() < 1e-12);
859        assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
860    }
861
862    #[test]
863    fn sub_reversed_bigfloat_minus_z() {
864        // 5 - (3 + 2i) = 2 - 2i
865        let z = BigComplex::from_f64(3.0, 2.0, PREC).expect("ok");
866        let d = BigFloat::from_i64(5, PREC, MODE);
867        let r = d - &z;
868        assert!((r.re().to_f64() - 2.0).abs() < 1e-12);
869        assert!((r.im().to_f64() + 2.0).abs() < 1e-12);
870    }
871
872    #[test]
873    fn bigfloat_scalar_assign_ops() {
874        let mut z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
875        let d3 = BigFloat::from_i64(3, PREC, MODE);
876        z += &d3;
877        assert!((z.re().to_f64() - 4.0).abs() < 1e-12);
878        assert!((z.im().to_f64() - 2.0).abs() < 1e-12);
879
880        let d1 = BigFloat::from_i64(1, PREC, MODE);
881        z -= d1;
882        assert!((z.re().to_f64() - 3.0).abs() < 1e-12);
883
884        let d2 = BigFloat::from_i64(2, PREC, MODE);
885        z *= &d2;
886        assert!((z.re().to_f64() - 6.0).abs() < 1e-12);
887        assert!((z.im().to_f64() - 4.0).abs() < 1e-12);
888    }
889
890    // -----------------------------------------------------------------------
891    // Scalar i64 ops
892    // -----------------------------------------------------------------------
893
894    #[test]
895    fn add_i64_scalar() {
896        let z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
897        let r = &z + 4i64;
898        assert!((r.re().to_f64() - 5.0).abs() < 1e-12);
899        assert!((r.im().to_f64() - 2.0).abs() < 1e-12);
900    }
901
902    #[test]
903    fn sub_i64_scalar() {
904        let z = BigComplex::from_f64(5.0, 3.0, PREC).expect("ok");
905        let r = z - 2i64;
906        assert!((r.re().to_f64() - 3.0).abs() < 1e-12);
907        assert!((r.im().to_f64() - 3.0).abs() < 1e-12);
908    }
909
910    #[test]
911    fn mul_i64_scalar_assign() {
912        let mut z = BigComplex::from_f64(1.0, 2.0, PREC).expect("ok");
913        z *= 3i64;
914        assert!((z.re().to_f64() - 3.0).abs() < 1e-12);
915        assert!((z.im().to_f64() - 6.0).abs() < 1e-12);
916    }
917
918    #[test]
919    fn sub_reversed_i64_minus_z() {
920        // 5 - (3 + 2i) = 2 - 2i
921        let z = BigComplex::from_f64(3.0, 2.0, PREC).expect("ok");
922        let r = 5i64 - &z;
923        assert!((r.re().to_f64() - 2.0).abs() < 1e-12);
924        assert!((r.im().to_f64() + 2.0).abs() < 1e-12);
925    }
926
927    #[test]
928    fn i64_scalar_ref_and_owned_consistent() {
929        let z = BigComplex::from_f64(2.0, 3.0, PREC).expect("ok");
930        let r1 = &z * 4i64;
931        let r2 = z.clone() * 4i64;
932        assert_eq!(r1, r2);
933    }
934}