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}