Skip to main content

dashu_float/
root.rs

1use dashu_base::{
2    ring::DivRem, Approximation, CubicRoot, EstimatedLog2, Sign, SquareRoot, SquareRootRem,
3    UnsignedAbs,
4};
5use dashu_int::{IBig, UBig};
6
7use crate::{
8    ball::Ball,
9    error::{assert_limited_precision, panic_root_zeroth, FpError, FpResult},
10    fbig::FBig,
11    repr::{Context, Repr, Word},
12    round::{mode, ErrorBounds, Round, Rounded, Rounding},
13    utils::{shl_digits, split_digits_ref},
14};
15
16/// Take the value of a [`Rounded`] result, recording in `exact` whether it was computed exactly.
17///
18/// Mirrors MPFR's `exact` flag: an all-exact operation chain yields the exact true value, which a
19/// Ziv closure can report with radius 0 — `ziv` then accepts it without the containment test,
20/// which otherwise can't certify an exactly-representable result (it sits on a one-sided preimage
21/// boundary under directed rounding).
22fn value_tracking_exact<T>(r: Rounded<T>, exact: &mut bool) -> T {
23    let (v, is_exact) = r.value_with_exact();
24    if !is_exact {
25        *exact = false;
26    }
27    v
28}
29
30impl<R: ErrorBounds, const B: Word> SquareRoot for FBig<R, B> {
31    type Output = Self;
32    #[inline]
33    fn sqrt(&self) -> Self {
34        self.context.unwrap_fp(self.context.sqrt(self.repr()))
35    }
36}
37
38impl<R: Round, const B: Word> CubicRoot for FBig<R, B> {
39    type Output = Self;
40    #[inline]
41    fn cbrt(&self) -> Self {
42        self.context.unwrap_fp(self.context.cbrt(self.repr()))
43    }
44}
45
46impl<R: Round, const B: Word> FBig<R, B> {
47    /// Calculate the nth root of the floating point number.
48    ///
49    /// When `n` is large the computation can be expensive — the significand is
50    /// padded to `n · precision` digits before the integer root is taken, and
51    /// the integer Newton iteration works with numbers of that size. For large
52    /// `n` consider [`powf`][`FBig::powf`] with a rational exponent `1 / n`
53    /// as a faster approximate alternative.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// # use core::str::FromStr;
59    /// # use dashu_base::ParseError;
60    /// # use dashu_float::DBig;
61    /// let a = DBig::from_str("16")?;
62    /// assert_eq!(a.nth_root(4), DBig::from_str("2")?);
63    /// # Ok::<(), ParseError>(())
64    /// ```
65    ///
66    /// # Panics
67    ///
68    /// Panics if `n` is zero, or if `n` is even and the number is negative.
69    #[inline]
70    pub fn nth_root(&self, n: usize) -> Self {
71        self.context
72            .unwrap_fp(self.context.nth_root(n, self.repr()))
73    }
74}
75
76impl<R: Round> Context<R> {
77    /// Calculate the cubic root of the floating point number.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// # use core::str::FromStr;
83    /// # use dashu_base::ParseError;
84    /// # use dashu_float::DBig;
85    /// use dashu_base::Approximation::*;
86    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
87    ///
88    /// let context = Context::<HalfAway>::new(2);
89    /// let a = DBig::from_str("8")?;
90    /// assert_eq!(context.cbrt(&a.repr()), Ok(Exact(DBig::from_str("2")?)));
91    /// # Ok::<(), ParseError>(())
92    /// ```
93    ///
94    /// # Panics
95    ///
96    /// Panics if the precision is unlimited.
97    #[inline]
98    pub fn cbrt<const B: Word>(&self, x: &Repr<B>) -> FpResult<FBig<R, B>> {
99        self.nth_root(3, x)
100    }
101
102    /// Calculate the nth root of the floating point number.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// # use core::str::FromStr;
108    /// # use dashu_base::ParseError;
109    /// # use dashu_float::DBig;
110    /// use dashu_base::Approximation::*;
111    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
112    ///
113    /// let context = Context::<HalfAway>::new(2);
114    /// let a = DBig::from_str("27")?;
115    /// assert_eq!(context.nth_root(3, &a.repr()), Ok(Exact(DBig::from_str("3")?)));
116    /// # Ok::<(), ParseError>(())
117    /// ```
118    ///
119    /// # Panics
120    ///
121    /// Panics if `n` is zero, if the precision is unlimited, or if `n` is even and `x` is negative.
122    pub fn nth_root<const B: Word>(&self, n: usize, x: &Repr<B>) -> FpResult<FBig<R, B>> {
123        if x.is_infinite() {
124            return Err(FpError::InfiniteInput);
125        }
126        assert_limited_precision(self.precision);
127        if n == 0 {
128            panic_root_zeroth()
129        }
130        debug_assert!(n < isize::MAX as usize);
131        let sign = x.sign();
132        if sign == Sign::Negative && n % 2 == 0 {
133            return Err(FpError::OutOfDomain);
134        }
135        if n == 1 {
136            return Ok(self.repr_round_ref(x).map(|v| FBig::new(v, *self)));
137        }
138        if x.significand.is_zero() {
139            // UBig::ZERO.nth_root(n) erroneously returns ONE, so short-circuit here.
140            // An even root of -0 already errored above, so reaching here the sign is
141            // preserved: odd root of ±0 is ±0.
142            return Ok(Approximation::Exact(FBig::new(x.clone(), *self)));
143        }
144
145        // operate on the magnitude so that shifting/splitting keep a clean sign;
146        // the original sign is re-applied to the result at the end.
147        let xmag: IBig = if sign == Sign::Negative {
148            -&x.significand
149        } else {
150            x.significand.clone()
151        };
152
153        // adjust the significand so that the exponent is divisible by n and the
154        // significand carries at least n*precision digits (required for rounding)
155        let digits = x.digits() as isize;
156        let r = (x.exponent + digits).rem_euclid(n as isize);
157        let shift = n as isize * self.precision as isize - digits + r;
158        let (signif, low, low_digits) = if shift > 0 {
159            (shl_digits::<B>(&xmag, shift as usize), IBig::ZERO, 0)
160        } else {
161            let shift = (-shift) as usize;
162            let (hi, lo) = split_digits_ref::<B>(&xmag, shift);
163            (hi, lo, shift)
164        };
165
166        let mag: UBig = signif.unsigned_abs();
167        let root: UBig = mag.nth_root(n);
168        let rem: UBig = &mag - root.clone().pow(n);
169        let exp = (x.exponent - shift) / n as isize;
170
171        let result_sign = if sign == Sign::Negative {
172            Sign::Negative
173        } else {
174            Sign::Positive
175        };
176        let signed_root: IBig = result_sign * root.clone();
177
178        let res = if rem.is_zero() && low.is_zero() {
179            Approximation::Exact(signed_root)
180        } else {
181            let adjust = R::round_low_part(&signed_root, result_sign, || {
182                // The true value is (mag + low / BASE^low_digits)^(1/n) and
183                // root = floor(mag^(1/n)); its fractional part is compared to 1/2.
184                // frac < 1/2  <=>  2^n * full < (2*root + 1)^n * BASE^low_digits,
185                // where full = mag * BASE^low_digits + low (the full significand).
186                let base_pow = Repr::<B>::BASE.pow(low_digits);
187                let full = &mag * &base_pow + low.unsigned_abs();
188                let lhs = full << n;
189                let rhs = ((root.clone() << 1) + UBig::from_word(1)).pow(n) * base_pow;
190                lhs.cmp(&rhs)
191            });
192            Approximation::Inexact(signed_root.clone() + adjust, adjust)
193        };
194        Ok(res
195            .map(|signif| Repr::new(signif, exp))
196            .and_then(|v| self.repr_round(v))
197            .map(|v| FBig::new(v, *self)))
198    }
199}
200
201impl<R: ErrorBounds> Context<R> {
202    /// Calculate the square root of the floating point number (correctly rounded).
203    ///
204    /// The integer square root of the exponent-aligned significand (which carries ~2·p digits) is
205    /// computed exactly, and its rounding to `p` digits is decided in a single step from the round
206    /// digit and the `sqrtrem` remainder (the sticky bit) — the same principle as MPFR. When the
207    /// root has `p + 1` digits it is rounded directly to `p` digits, avoiding the double rounding
208    /// that a rem-vs-root + re-round path incurs. For a power-of-two base this fast path is already
209    /// correctly rounded; for other bases the result is additionally certified by a Ziv loop (the
210    /// base-`B` digit alignment of an integer square root is only clean when the base is a power of
211    /// two).
212    ///
213    /// # Panics
214    ///
215    /// Panics if the precision is unlimited.
216    pub fn sqrt<const B: Word>(&self, x: &Repr<B>) -> FpResult<FBig<R, B>> {
217        if x.is_infinite() {
218            return Err(FpError::InfiniteInput);
219        }
220        if x.significand.is_zero() {
221            // sqrt(+0) = +0, sqrt(-0) = -0 (preserve the sign of zero). Exact, so handle
222            // it before the limited-precision assertion: a precision-0 (unlimited) value
223            // such as the one from `try_from(0.0)` must still compute sqrt(0) exactly.
224            return Ok(Approximation::Exact(FBig::new(x.clone(), *self)));
225        }
226        assert_limited_precision(self.precision);
227        if x.sign() == Sign::Negative {
228            return Err(FpError::OutOfDomain);
229        }
230
231        // One-step correctly-rounded sqrt of the (finite, positive, limited) input at a working
232        // precision, used directly by the power-of-two fast path and by the Ziv loop otherwise.
233        let sqrt_rounded = |guard: usize| -> FpResult<FBig<R, B>> {
234            let gctx = Context::<R>::new(self.precision + guard);
235            let p = gctx.precision;
236
237            // Adjust the significand so the exponent is even, with ~2p significant digits.
238            let digits = x.digits() as isize;
239            let shift = p as isize * 2 - (digits & 1) + (x.exponent & 1) - digits;
240            let (signif, low, low_digits) = if shift > 0 {
241                (shl_digits::<B>(&x.significand, shift as usize), IBig::ZERO, 0)
242            } else {
243                let shift = (-shift) as usize;
244                let (hi, lo) = split_digits_ref::<B>(&x.significand, shift);
245                (hi, lo, shift)
246            };
247
248            let (root, rem) = signif.unsigned_abs().sqrt_rem();
249            let exp = (x.exponent - shift) / 2;
250            let exact = rem.is_zero() && low.is_zero();
251            // The root has `p` or `p+1` base-B digits, decided exactly in O(1) from the shifted
252            // significand's digit count: `signif` carries `2p − (digits&1) + (exp&1)` digits, so it
253            // has 2p+1 digits (root has p+1) exactly when the significand's digit count is even and
254            // the input exponent is odd.
255            let root_is_p1 = (digits & 1) == 0 && (x.exponent & 1) == 1;
256
257            // The result's exponent. A p+1-digit root is rounded to p digits by dropping the lowest
258            // base-B digit (`r = root / B`), which shifts the value by one base-B digit: `exp + 1`.
259            // The arithmetic works on the unsigned `root`; it is converted to `IBig` only where
260            // `round_low_part` (signed) and the result's `+ Rounding` need it.
261            let (sig, adjust, result_exp) = if !root_is_p1 {
262                // p-digit root: the remainder (and any truncated input) decide the rounding. An
263                // integer sqrt has no exact half-tie (`sqrt(n) = root + 1/2` would require
264                // `4·rem = 2·root + 1`, impossible), so the rem-vs-root comparison is the exact
265                // single rounding.
266                let adjust = if exact {
267                    Rounding::NoOp
268                } else {
269                    R::round_low_part(root.as_ibig(), Sign::Positive, || {
270                        rem.cmp(&root)
271                            .then_with(|| (low << 2).cmp(&Repr::<B>::BASE.pow(low_digits).into()))
272                    })
273                };
274                (IBig::from(root), adjust, exp)
275            } else {
276                // p+1-digit root: round to p digits in one step. `r` = top p digits, `d` = round
277                // digit (a Word, from the single `div_rem`).
278                let (r, d) = root.div_rem(B);
279                let adjust = if exact {
280                    // The exact p+1-digit root: a clean rounding of `2·d` vs `B`, ties per the mode
281                    // (`2·d = B` is the real half-tie; `d vs ⌊B/2⌋` would mis-round odd bases).
282                    R::round_low_part(r.as_ibig(), Sign::Positive, || (d * 2).cmp(&B))
283                } else {
284                    // The true value is strictly above `root` (sticky remainder / truncated input),
285                    // so a round digit at the real half (2·d = B) still rounds up.
286                    if d * 2 >= B {
287                        Rounding::AddOne
288                    } else {
289                        Rounding::NoOp
290                    }
291                };
292                (IBig::from(r), adjust, exp + 1)
293            };
294
295            let res = if exact && !root_is_p1 {
296                Approximation::Exact(sig)
297            } else {
298                Approximation::Inexact(sig + adjust, adjust)
299            };
300            Ok(res
301                .map(|signif| Repr::new(signif, result_exp))
302                .and_then(|v| gctx.repr_round(v))
303                .map(|v| FBig::new(v, gctx)))
304        };
305
306        if B.is_power_of_two() {
307            sqrt_rounded(0)
308        } else {
309            self.ziv(crate::utils::ceil_usize(self.precision.log2_est()) + 10, |guard| {
310                let value = sqrt_rounded(guard)?.value();
311                let radius = value.clone().ulp();
312                Ok((value, radius))
313            })
314        }
315    }
316
317    /// Compute `sqrt(a² + b²)` without spurious overflow/underflow.
318    ///
319    /// This is the overflow-safe scaled sum-of-squares: the larger-magnitude operand is never
320    /// squared. Writing `m = max(|a|, |b|)` and `r = min(|a|,|b|) / m` (so `|r| ≤ 1`), the result is
321    /// `m · sqrt(1 + r²)`, where `1 + r² ∈ [1, 2]` cannot overflow. The result is correctly rounded
322    /// via a Ziv retry loop (`hypot(±inf, ·) = +inf`, `hypot(0, 0) = +0`).
323    ///
324    /// This is a field-arithmetic-class op (no constant cache), like `sqrt`/`atan2`.
325    ///
326    /// # Panics
327    ///
328    /// Panics if the precision is unlimited.
329    pub fn hypot<const B: Word>(&self, a: &Repr<B>, b: &Repr<B>) -> FpResult<FBig<R, B>> {
330        if a.is_infinite() || b.is_infinite() {
331            return Ok(Approximation::Exact(FBig::new(Repr::infinity(), *self)));
332        }
333        assert_limited_precision(self.precision);
334        if a.significand.is_zero() && b.significand.is_zero() {
335            return Ok(Approximation::Exact(FBig::new(Repr::zero(), *self)));
336        }
337
338        // magnitudes, ordered large >= small (both finite, not both zero here)
339        let a_mag = if a.sign() == Sign::Negative {
340            -a.clone()
341        } else {
342            a.clone()
343        };
344        let b_mag = if b.sign() == Sign::Negative {
345            -b.clone()
346        } else {
347            b.clone()
348        };
349        let (large, small) = if a_mag.cmp(&b_mag).is_ge() {
350            (a_mag, b_mag)
351        } else {
352            (b_mag, a_mag)
353        };
354
355        if small.significand.is_zero() {
356            // hypot(x, 0) = |x|; `large` is already a magnitude.
357            return Ok(self.repr_round_ref(&large).map(|v| FBig::new(v, *self)));
358        }
359
360        // The result is `sqrt(large² + small²)`, i.e. ∈ [large, large·√2]. It overflows only when
361        // `large` is so large that the result reaches the infinity sentinel exponent — unreachable
362        // for real inputs, but pre-checked here so the Ziv closure can use infallible `FBig`
363        // arithmetic.
364        if large.exponent >= isize::MAX - 1 {
365            return Err(FpError::Overflow(Sign::Positive));
366        }
367
368        let initial_guard = crate::utils::ceil_usize(self.precision.log2_est()) + 10;
369        self.ziv(initial_guard, |guard| {
370            let gctx = Context::<mode::HalfEven>::new(self.precision + guard);
371            // result = sqrt(large² + small²), with both operands scaled down by `k` base-B digits
372            // before squaring (so `large²` can't overflow the exponent) and the root scaled back:
373            // sqrt(L² + S²) · B^k = sqrt(large² + small²) for L = large·B⁻ᵏ, S = small·B⁻ᵏ. No
374            // division — so for integer inputs every step is exact (MPFR's `exact` flag), and an
375            // all-exact chain yields the exact true value. The tracking variants report radius 0
376            // then, which `ziv` accepts without the containment test (it can't certify an
377            // exactly-representable result under directed rounding — e.g. hypot(3,4)=5,
378            // hypot(5,12)=13 — which sits on a one-sided preimage boundary).
379            let k = (large.exponent as i128 - (isize::MAX as i128 - 2) / 2).max(0) as isize;
380            let mut exact = true;
381            let large_ball = Ball::exact(FBig::new(
382                value_tracking_exact(gctx.repr_round_ref(&large), &mut exact),
383                gctx,
384            ));
385            let small_ball = Ball::exact(FBig::new(
386                value_tracking_exact(gctx.repr_round_ref(&small), &mut exact),
387                gctx,
388            ));
389            // The shifted balls are used twice (the square), so bind them once — a shift is a full
390            // O(p) clone otherwise.
391            let l = large_ball.shift(k);
392            let s = small_ball.shift(k);
393            let l_sq = l.mul_tracking(&l, &mut exact)?;
394            let s_sq = s.mul_tracking(&s, &mut exact)?;
395            let sum = l_sq.add_tracking(&s_sq, &mut exact)?;
396            let root = sum.sqrt_tracking(&mut exact)?;
397            let result = root.shift(-k); // exact exponent shift — scales back, doesn't affect `exact`
398                                         // An all-exact chain yields n = 0, which `to_value_radius` already reports as a zero
399                                         // radius (the exactly-representable directed-rounding case).
400            Ok(result.to_value_radius::<R>())
401        })
402    }
403}
404
405impl<R: ErrorBounds, const B: Word> FBig<R, B> {
406    /// Calculate the square root of the floating point number (correctly rounded).
407    ///
408    /// # Panics
409    ///
410    /// Panics if the precision is unlimited.
411    #[inline]
412    pub fn sqrt(&self) -> Self {
413        self.context.unwrap_fp(self.context.sqrt(&self.repr))
414    }
415
416    /// Compute `sqrt(self² + other²)` without spurious overflow/underflow.
417    ///
418    /// The result precision is `max(self.precision(), other.precision())`. See
419    /// [`Context::hypot`] for the overflow-safety strategy.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// # use core::str::FromStr;
425    /// # use dashu_base::ParseError;
426    /// # use dashu_float::DBig;
427    /// let a = DBig::from_str("3")?;
428    /// let b = DBig::from_str("4")?;
429    /// assert_eq!(a.hypot(&b), DBig::from_str("5")?);
430    /// # Ok::<(), ParseError>(())
431    /// ```
432    ///
433    /// # Panics
434    ///
435    /// Panics if the precision is unlimited.
436    #[inline]
437    pub fn hypot(&self, other: &Self) -> Self {
438        let context = Context::max(self.context, other.context);
439        context.unwrap_fp(context.hypot(&self.repr, &other.repr))
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::round::mode;
447
448    #[test]
449    #[should_panic]
450    fn test_fbig_sqrt_negative_panics() {
451        // sqrt(-1) is out of domain; the FBig layer panics.
452        let neg_one = FBig::<mode::HalfEven>::try_from(-1.0f64).unwrap();
453        let _ = neg_one.sqrt();
454    }
455
456    #[test]
457    fn test_hypot_pythagorean() {
458        let ctx = Context::<mode::HalfEven>::new(53);
459        let mk = |v: i32| Repr::<2>::new(v.into(), 0);
460        // hypot(3, 4) = 5
461        let r = ctx.hypot(&mk(3), &mk(4)).unwrap().value();
462        assert_eq!(r.repr().significand(), &5.into());
463        // hypot(5, 0) = 5
464        let r = ctx.hypot(&mk(5), &mk(0)).unwrap().value();
465        assert_eq!(r.repr().significand(), &5.into());
466        // hypot(0, 0) = 0
467        let r = ctx.hypot(&mk(0), &mk(0)).unwrap().value();
468        assert!(r.repr().is_pos_zero());
469        // hypot(inf, x) = +inf
470        let r = ctx.hypot(&Repr::infinity(), &mk(3)).unwrap().value();
471        assert!(r.repr().is_infinite());
472        assert_eq!(r.repr().sign(), Sign::Positive);
473    }
474
475    fn check_hypot_exact_triples<R: ErrorBounds>(ctx: Context<R>) {
476        let mk = |v: i32| Repr::<2>::new(v.into(), 0);
477        // Pythagorean triples: the result is exactly representable, so under a directed mode it
478        // sits on a one-sided preimage boundary. The closure must terminate (radius 0 from the
479        // all-exact `sqrt(large²+small²)` chain) rather than infinite-retry.
480        for (a, b, h) in [(3, 4, 5), (5, 12, 13), (8, 15, 17)] {
481            let r = ctx.hypot(&mk(a), &mk(b)).unwrap().value();
482            assert_eq!(r.repr().significand(), &h.into(), "hypot({a}, {b})");
483        }
484    }
485
486    #[test]
487    fn test_hypot_exact_under_directed_rounding() {
488        check_hypot_exact_triples(Context::<mode::Down>::new(53));
489        check_hypot_exact_triples(Context::<mode::Up>::new(53));
490        check_hypot_exact_triples(Context::<mode::Zero>::new(53));
491    }
492
493    #[test]
494    fn test_hypot_no_spurious_overflow() {
495        // a value whose square would collide with the +inf sentinel exponent, but whose
496        // hypot is itself representable: hypot(a, 0) = |a| must not overflow via a².
497        let ctx = Context::<mode::HalfEven>::new(53);
498        // exponent near isize::MAX/2 so that a² would overflow, but |a| is fine
499        let a = Repr::<2>::new(IBig::from(3), isize::MAX / 2);
500        let r = ctx.hypot(&a, &Repr::<2>::zero()).unwrap().value();
501        assert_eq!(r.repr().exponent(), isize::MAX / 2);
502    }
503}