Skip to main content

dashu_float/
root.rs

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