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}