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}