Skip to main content

dashu_cmplx/
root.rs

1//! Complex square root (principal branch; cut on `]−∞, 0]`).
2
3use crate::cbig::CBig;
4use crate::repr::{combine_parts, exact, CfpResult, Context};
5use dashu_base::Sign;
6use dashu_float::round::Round;
7use dashu_float::{FBig, Repr};
8use dashu_int::Word;
9
10/// Guard digits (base-B) for `sqrt`. Composes `hypot` + two real `sqrt`s + adds; a modest fixed
11/// guard absorbs the accumulated rounding.
12const SQRT_GUARD: usize = 12;
13
14/// A signed-infinity [`Repr`] (the public-API stand-in for the private `infinity_with_sign`).
15fn signed_inf<const B: Word>(sign: Sign) -> Repr<B> {
16    match sign {
17        Sign::Positive => Repr::infinity(),
18        Sign::Negative => Repr::neg_infinity(),
19    }
20}
21
22impl<R: Round> Context<R> {
23    /// Principal square root of a complex number (context layer).
24    ///
25    /// The result has non-negative real part; when the real part is zero the imaginary part is
26    /// non-negative. The branch cut lies on `]−∞, 0]`; `sqrt(conj z) == conj(sqrt z)` holds, which
27    /// signed zero makes continuous across the cut.
28    pub fn sqrt<const B: Word>(&self, z: &CBig<R, B>) -> CfpResult<R, B> {
29        if let Some(special) = sqrt_special(z, *self) {
30            return special;
31        }
32
33        let gctx = self.guard(SQRT_GUARD);
34        let p = self.precision();
35        let two = FBig::from_repr(Repr::new(2.into(), 0), gctx);
36        let x = z.re();
37        let y = z.im();
38
39        // r = |z| (overflow-safe). Use the cancellation-free form: for x ≥ 0 compute `a` from
40        // `(r+x)/2` (large) and `b = y/(2a)`; for x < 0 compute `b` from `(r-x)/2` (large) and
41        // `a = y/(2b)`. This avoids subtracting nearly-equal magnitudes when |y| ≪ |x|.
42        let r = gctx.hypot(x, y)?.value();
43        let (a, b) = if x.sign() != Sign::Negative {
44            // x ≥ 0
45            let rpx = gctx.add(r.repr(), x)?.value();
46            let half_rpx = gctx.div(rpx.repr(), two.repr())?.value();
47            let a = gctx.sqrt(half_rpx.repr())?.value();
48            let two_a = gctx.mul(two.repr(), a.repr())?.value();
49            let b = gctx.div(y, two_a.repr())?.value();
50            (a, b)
51        } else {
52            // x < 0: b carries the sign of y
53            let rmx = gctx.sub(r.repr(), x)?.value(); // r − x = r + |x|
54            let half_rmx = gctx.div(rmx.repr(), two.repr())?.value();
55            let b_mag = gctx.sqrt(half_rmx.repr())?.value();
56            let b = if y.sign() == Sign::Negative {
57                -b_mag
58            } else {
59                b_mag
60            };
61            let two_b = gctx.mul(two.repr(), b.repr())?.value();
62            let a = gctx.div(y, two_b.repr())?.value();
63            (a, b)
64        };
65        let re = a.with_precision(p);
66        let im = b.with_precision(p);
67        Ok(combine_parts(re, im))
68    }
69}
70
71impl<R: Round, const B: Word> CBig<R, B> {
72    /// Principal square root (convenience layer).
73    ///
74    /// # Panics
75    ///
76    /// Panics if the precision is unlimited, or on an out-of-domain / indeterminate special value.
77    #[inline]
78    pub fn sqrt(&self) -> Self {
79        self.context().unwrap_cfp(self.context().sqrt(self))
80    }
81}
82
83/// Annex G `csqrt` special-value table (the subset expressible without NaN).
84fn sqrt_special<R: Round, const B: Word>(
85    z: &CBig<R, B>,
86    ctx: Context<R>,
87) -> Option<CfpResult<R, B>> {
88    let f = ctx.float();
89    // sqrt(±0 + i·0) = ±0 + i·0 (preserve the real sign of zero)
90    if z.is_zero() {
91        return Some(Ok(exact(
92            FBig::from_repr(z.re().clone(), f),
93            FBig::from_repr(z.im().clone(), f),
94        )));
95    }
96    if !z.is_infinite() {
97        return None;
98    }
99
100    let x_pos_inf = z.re().is_infinite() && z.re().sign() == Sign::Positive;
101    let y_sign = z.im().sign();
102
103    let (re, im) = if z.im().is_infinite() {
104        // sqrt(x ± i·inf) = +inf ± i·inf for ANY x — Annex G: the infinite imaginary part
105        // dominates whether the real part is finite or infinite. This must be checked before the
106        // x-infinite cases below, which only apply when the imaginary part is finite.
107        (Repr::infinity(), signed_inf::<B>(y_sign))
108    } else if x_pos_inf {
109        // sqrt(+inf + iy) = +inf + i·0, finite y (the zero carries the sign of y)
110        (
111            Repr::infinity(),
112            if y_sign == Sign::Negative {
113                Repr::neg_zero()
114            } else {
115                Repr::zero()
116            },
117        )
118    } else {
119        // sqrt(-inf + iy) = +0 + i·sign(y)·inf, finite y (x is -inf, since z is infinite)
120        (Repr::zero(), signed_inf::<B>(y_sign))
121    };
122    Some(Ok(exact(FBig::from_repr(re, f), FBig::from_repr(im, f))))
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use dashu_float::round::mode;
129
130    type C = CBig<mode::HalfAway, 10>;
131    type F = FBig<mode::HalfAway, 10>;
132
133    fn c(re: i32, im: i32) -> C {
134        let mk = |v: i32| -> F { F::from(v).with_precision(53).value() };
135        CBig::from_parts(mk(re), mk(im))
136    }
137
138    #[test]
139    fn sqrt_basic() {
140        // sqrt(3+4i) = 2+i  (since (2+i)² = 3+4i)
141        let z = c(3, 4);
142        let s = z.sqrt();
143        let chk = &s * &s;
144        assert!(chk == z);
145    }
146
147    #[test]
148    fn sqrt_infinite_imaginary_dominates() {
149        // Annex G: sqrt(x ± i·inf) = +inf ± i·inf for ANY x, including x = ±inf — the infinite
150        // imaginary part must be handled before the x-infinite cases.
151        let ctx = Context::<mode::HalfAway>::new(53);
152        let cases = [
153            C::new(Repr::infinity(), Repr::infinity(), ctx),
154            C::new(Repr::neg_infinity(), Repr::infinity(), ctx),
155        ];
156        for z in cases {
157            let s = z.sqrt();
158            assert!(s.re().is_infinite() && s.re().sign() == Sign::Positive);
159            assert!(s.im().is_infinite() && s.im().sign() == Sign::Positive);
160        }
161    }
162
163    #[test]
164    fn sqrt_real() {
165        // sqrt(9+0i) = 3+0i
166        let z = c(9, 0);
167        let s = z.sqrt();
168        assert!(s == c(3, 0));
169    }
170
171    #[test]
172    fn sqrt_negative_real_is_imaginary() {
173        // sqrt(-4+0i) = 0+2i
174        let z = c(-4, 0);
175        let s = z.sqrt();
176        assert!(s.re().significand().is_zero());
177        assert_eq!(s.im().significand(), &2.into());
178    }
179
180    #[test]
181    fn sqrt_conj_identity() {
182        // sqrt(conj z) == conj(sqrt z)
183        let z = c(3, 4);
184        let lhs = z.conj().sqrt();
185        let rhs = z.sqrt().conj();
186        assert!(lhs == rhs);
187    }
188
189    #[test]
190    fn sqrt_zero() {
191        let s = C::ZERO.sqrt();
192        assert!(s.is_zero());
193    }
194
195    #[test]
196    fn sqrt_pos_infinity() {
197        let inf = CBig::from(F::INFINITY);
198        let s = inf.sqrt();
199        assert!(s.re().is_infinite());
200        assert!(s.im().is_pos_zero());
201    }
202}