Skip to main content

dashu_cmplx/
mul.rs

1//! Complex squaring and multiplication (near-correctly rounded via the guard-digit recipe).
2
3use crate::cbig::CBig;
4use crate::repr::{combine_parts, exact, riemann, CfpResult, Context};
5use core::ops::{Mul, MulAssign};
6use dashu_float::round::Round;
7use dashu_float::{FBig, FpError};
8use dashu_int::Word;
9
10/// Guard digits (base-B) for `sqr`/`mul`. The published normwise error bound for complex
11/// multiplication is `< √5·u` (Brent–Percival–Zimmermann), so a small fixed guard comfortably
12/// settles the accumulated rounding of the 2–4 component products for non-cancelling inputs.
13const MUL_GUARD: usize = 10;
14
15impl<R: Round> Context<R> {
16    /// Square a complex number under this context: `(x+iy)² = (x²-y²) + i(2xy)`.
17    pub fn sqr<const B: Word>(&self, z: &CBig<R, B>) -> CfpResult<R, B> {
18        if z.is_infinite() {
19            return Ok(riemann(*self)); // ∞·∞ = Riemann infinity
20        }
21        if z.is_zero() {
22            return Ok(exact(FBig::ZERO, FBig::ZERO));
23        }
24        let gctx = self.guard(MUL_GUARD);
25        let p = self.precision();
26        let (x, y) = (z.re(), z.im());
27        // real part: x² - y²
28        let x2 = gctx.sqr(x)?.value();
29        let y2 = gctx.sqr(y)?.value();
30        let re = gctx.sub(x2.repr(), y2.repr())?.value().with_precision(p);
31        // imaginary part: 2·x·y
32        let xy = gctx.mul(x, y)?.value();
33        let im = gctx.add(xy.repr(), xy.repr())?.value().with_precision(p);
34        Ok(combine_parts(re, im))
35    }
36
37    /// Multiply two complex numbers under this context: `(x+iy)(u+iv) = (xu-yv) + i(xv+yu)`
38    /// (naive 4-mul form; near-correctly rounded via the guard re-round).
39    pub fn mul<const B: Word>(&self, z: &CBig<R, B>, w: &CBig<R, B>) -> CfpResult<R, B> {
40        if z.is_infinite() || w.is_infinite() {
41            if z.is_zero() || w.is_zero() {
42                return Err(FpError::Indeterminate); // 0·∞
43            }
44            return Ok(riemann(Context::max(z.context(), w.context()))); // ∞·finite = Riemann infinity
45        }
46        let gctx = self.guard(MUL_GUARD);
47        let p = self.precision();
48        let (x, y) = (z.re(), z.im());
49        let (u, v) = (w.re(), w.im());
50        // real part: xu - yv
51        let xu = gctx.mul(x, u)?.value();
52        let yv = gctx.mul(y, v)?.value();
53        let re = gctx.sub(xu.repr(), yv.repr())?.value().with_precision(p);
54        // imaginary part: xv + yu
55        let xv = gctx.mul(x, v)?.value();
56        let yu = gctx.mul(y, u)?.value();
57        let im = gctx.add(xv.repr(), yu.repr())?.value().with_precision(p);
58        Ok(combine_parts(re, im))
59    }
60
61    /// Multiply a complex number by a real scalar (context layer): `(x+iy)·s = (xs) + i(ys)`.
62    pub fn mul_real<const B: Word>(&self, z: &CBig<R, B>, s: &FBig<R, B>) -> CfpResult<R, B> {
63        if z.is_infinite() || s.repr().is_infinite() {
64            if z.is_zero() || s.repr().is_pos_zero() || s.repr().is_neg_zero() {
65                return Err(FpError::Indeterminate); // 0·∞
66            }
67            return Ok(riemann(*self));
68        }
69        let gctx = self.guard(MUL_GUARD);
70        let p = self.precision();
71        let re = gctx.mul(z.re(), s.repr())?.value().with_precision(p);
72        let im = gctx.mul(z.im(), s.repr())?.value().with_precision(p);
73        Ok(combine_parts(re, im))
74    }
75}
76
77impl<R: Round, const B: Word> CBig<R, B> {
78    /// Square the complex number (convenience layer).
79    #[inline]
80    pub fn sqr(&self) -> Self {
81        self.context().unwrap_cfp(self.context().sqr(self))
82    }
83}
84
85// CBig · CBig operators — forwarded through the standard macro (mirroring `dashu-float`'s `mul.rs`).
86crate::helper_macros::impl_cbig_binop!(Mul, mul, MulAssign, mul_assign);
87
88// --- scalar multiplication by a real FBig (mixed-type operators) ---
89
90// CBig · FBig (componentwise, via the shared scalar macro).
91crate::helper_macros::impl_cbig_scalar_binop!(Mul, mul, mul_real);
92
93// FBig · CBig (commutative: FBig·CBig = CBig·FBig).
94impl<R: Round, const B: Word> Mul<&CBig<R, B>> for &FBig<R, B> {
95    type Output = CBig<R, B>;
96    #[inline]
97    fn mul(self, rhs: &CBig<R, B>) -> CBig<R, B> {
98        rhs * self
99    }
100}
101impl<R: Round, const B: Word> Mul<CBig<R, B>> for &FBig<R, B> {
102    type Output = CBig<R, B>;
103    #[inline]
104    fn mul(self, rhs: CBig<R, B>) -> CBig<R, B> {
105        &rhs * self
106    }
107}
108impl<R: Round, const B: Word> Mul<&CBig<R, B>> for FBig<R, B> {
109    type Output = CBig<R, B>;
110    #[inline]
111    fn mul(self, rhs: &CBig<R, B>) -> CBig<R, B> {
112        rhs * &self
113    }
114}
115impl<R: Round, const B: Word> Mul<CBig<R, B>> for FBig<R, B> {
116    type Output = CBig<R, B>;
117    #[inline]
118    fn mul(self, rhs: CBig<R, B>) -> CBig<R, B> {
119        &rhs * &self
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use dashu_float::round::mode;
127
128    type C = CBig<mode::HalfAway, 10>;
129    type F = FBig<mode::HalfAway, 10>;
130
131    fn c(re: i32, im: i32) -> C {
132        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
133        C::from_parts(mk(re), mk(im))
134    }
135
136    #[test]
137    fn sqr_basic() {
138        // (3+4i)² = -7+24i
139        let z = c(3, 4);
140        let s = z.sqr();
141        assert_eq!(s.re().significand(), &(-7i32).into());
142        assert_eq!(s.im().significand(), &24.into());
143    }
144
145    #[test]
146    fn mul_basic() {
147        // (1+2i)(3+4i) = -5+10i  (compare full values: 10 normalizes to 1·10¹ in base 10)
148        let z = c(1, 2);
149        let w = c(3, 4);
150        let p = &z * &w;
151        assert!(p == c(-5, 10));
152    }
153
154    #[test]
155    fn mul_assign_val_and_ref() {
156        let z = c(1, 2);
157        let w = c(3, 4);
158        // (1+2i)(3+4i) = -5+10i
159        let mut acc = z.clone();
160        acc *= w.clone();
161        assert!(acc == c(-5, 10));
162        let mut acc = z.clone();
163        acc *= &w;
164        assert!(acc == c(-5, 10));
165    }
166
167    #[test]
168    fn mul_by_one_is_identity() {
169        let z = c(3, 4);
170        let p = &z * &CBig::ONE;
171        assert!(p == z);
172    }
173
174    #[test]
175    fn mul_by_conj_is_norm() {
176        // z·conj(z) = norm(z), purely real
177        let z = c(3, 4);
178        let p = &z * &z.conj();
179        assert!(p.im().is_pos_zero() || p.im().is_neg_zero());
180        assert_eq!(p.re().significand(), &25.into());
181    }
182
183    #[test]
184    fn scalar_mul_by_real() {
185        let z = c(3, 4);
186        let s = FBig::<mode::HalfAway, 10>::from(2);
187        let p = &z * &s;
188        assert_eq!(p.re().significand(), &6.into());
189        assert_eq!(p.im().significand(), &8.into());
190        // commutes: s * z
191        let p2 = &s * &z;
192        assert_eq!(p2.re().significand(), &6.into());
193    }
194}