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