dashu_float/root.rs
1use dashu_base::{Approximation, CubicRoot, Sign, SquareRoot, SquareRootRem, UnsignedAbs};
2use dashu_int::{IBig, UBig};
3
4use crate::{
5 error::{assert_finite, assert_limited_precision, panic_root_negative, panic_root_zeroth},
6 fbig::FBig,
7 repr::{Context, Repr, Word},
8 round::{Round, Rounded},
9 utils::{shl_digits, split_digits_ref},
10};
11
12impl<R: Round, const B: Word> SquareRoot for FBig<R, B> {
13 type Output = Self;
14 #[inline]
15 fn sqrt(&self) -> Self {
16 self.context.sqrt(self.repr()).value()
17 }
18}
19
20impl<R: Round, const B: Word> CubicRoot for FBig<R, B> {
21 type Output = Self;
22 #[inline]
23 fn cbrt(&self) -> Self {
24 self.context.cbrt(self.repr()).value()
25 }
26}
27
28impl<R: Round, const B: Word> FBig<R, B> {
29 /// Calculate the nth root of the floating point number.
30 ///
31 /// When `n` is large the computation can be expensive — the significand is
32 /// padded to `n · precision` digits before the integer root is taken, and
33 /// the integer Newton iteration works with numbers of that size. For large
34 /// `n` consider [`powf`][`FBig::powf`] with a rational exponent `1 / n`
35 /// as a faster approximate alternative.
36 ///
37 /// # Examples
38 ///
39 /// ```
40 /// # use core::str::FromStr;
41 /// # use dashu_base::ParseError;
42 /// # use dashu_float::DBig;
43 /// let a = DBig::from_str("16")?;
44 /// assert_eq!(a.nth_root(4), DBig::from_str("2")?);
45 /// # Ok::<(), ParseError>(())
46 /// ```
47 ///
48 /// # Panics
49 ///
50 /// Panics if `n` is zero, or if `n` is even and the number is negative.
51 #[inline]
52 pub fn nth_root(&self, n: usize) -> Self {
53 self.context.nth_root(n, self.repr()).value()
54 }
55}
56
57impl<R: Round> Context<R> {
58 /// Calculate the square root of the floating point number.
59 ///
60 /// # Examples
61 ///
62 /// ```
63 /// # use core::str::FromStr;
64 /// # use dashu_base::ParseError;
65 /// # use dashu_float::DBig;
66 /// use dashu_base::Approximation::*;
67 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
68 ///
69 /// let context = Context::<HalfAway>::new(2);
70 /// let a = DBig::from_str("1.23")?;
71 /// assert_eq!(context.sqrt(&a.repr()), Inexact(DBig::from_str("1.1")?, NoOp));
72 /// # Ok::<(), ParseError>(())
73 /// ```
74 ///
75 /// # Panics
76 ///
77 /// Panics if the precision is unlimited.
78 pub fn sqrt<const B: Word>(&self, x: &Repr<B>) -> Rounded<FBig<R, B>> {
79 assert_finite(x);
80 assert_limited_precision(self.precision);
81 if x.sign() == Sign::Negative {
82 panic_root_negative()
83 }
84
85 // adjust the signifcand so that the exponent is even
86 let digits = x.digits() as isize;
87 let shift = self.precision as isize * 2 - (digits & 1) + (x.exponent & 1) - digits;
88 let (signif, low, low_digits) = if shift > 0 {
89 (shl_digits::<B>(&x.significand, shift as usize), IBig::ZERO, 0)
90 } else {
91 let shift = (-shift) as usize;
92 let (hi, lo) = split_digits_ref::<B>(&x.significand, shift);
93 (hi, lo, shift)
94 };
95
96 let (root, rem) = signif.unsigned_abs().sqrt_rem();
97 let root = Sign::Positive * root;
98 let exp = (x.exponent - shift) / 2;
99
100 let res = if rem.is_zero() {
101 Approximation::Exact(root)
102 } else {
103 let adjust = R::round_low_part(&root, Sign::Positive, || {
104 (Sign::Positive * rem)
105 .cmp(&root)
106 .then_with(|| (low * 4u8).cmp(&Repr::<B>::BASE.pow(low_digits).into()))
107 });
108 Approximation::Inexact(root + adjust, adjust)
109 };
110 res.map(|signif| Repr::new(signif, exp))
111 .and_then(|v| self.repr_round(v))
112 .map(|v| FBig::new(v, *self))
113 }
114
115 /// Calculate the cubic root of the floating point number.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// # use core::str::FromStr;
121 /// # use dashu_base::ParseError;
122 /// # use dashu_float::DBig;
123 /// use dashu_base::Approximation::*;
124 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
125 ///
126 /// let context = Context::<HalfAway>::new(2);
127 /// let a = DBig::from_str("8")?;
128 /// assert_eq!(context.cbrt(&a.repr()), Exact(DBig::from_str("2")?));
129 /// # Ok::<(), ParseError>(())
130 /// ```
131 ///
132 /// # Panics
133 ///
134 /// Panics if the precision is unlimited.
135 #[inline]
136 pub fn cbrt<const B: Word>(&self, x: &Repr<B>) -> Rounded<FBig<R, B>> {
137 self.nth_root(3, x)
138 }
139
140 /// Calculate the nth root of the floating point number.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// # use core::str::FromStr;
146 /// # use dashu_base::ParseError;
147 /// # use dashu_float::DBig;
148 /// use dashu_base::Approximation::*;
149 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
150 ///
151 /// let context = Context::<HalfAway>::new(2);
152 /// let a = DBig::from_str("27")?;
153 /// assert_eq!(context.nth_root(3, &a.repr()), Exact(DBig::from_str("3")?));
154 /// # Ok::<(), ParseError>(())
155 /// ```
156 ///
157 /// # Panics
158 ///
159 /// Panics if `n` is zero, if the precision is unlimited, or if `n` is even and `x` is negative.
160 pub fn nth_root<const B: Word>(&self, n: usize, x: &Repr<B>) -> Rounded<FBig<R, B>> {
161 assert_finite(x);
162 assert_limited_precision(self.precision);
163 if n == 0 {
164 panic_root_zeroth()
165 }
166 debug_assert!(n < isize::MAX as usize);
167 let sign = x.sign();
168 if sign == Sign::Negative && n % 2 == 0 {
169 panic_root_negative()
170 }
171 if n == 1 {
172 return self.repr_round_ref(x).map(|v| FBig::new(v, *self));
173 }
174 if x.significand.is_zero() {
175 // UBig::ZERO.nth_root(n) erroneously returns ONE, so short-circuit here
176 return Approximation::Exact(FBig::new(Repr::zero(), *self));
177 }
178
179 // operate on the magnitude so that shifting/splitting keep a clean sign;
180 // the original sign is re-applied to the result at the end.
181 let xmag: IBig = if sign == Sign::Negative {
182 -&x.significand
183 } else {
184 x.significand.clone()
185 };
186
187 // adjust the significand so that the exponent is divisible by n and the
188 // significand carries at least n*precision digits (required for rounding)
189 let digits = x.digits() as isize;
190 let r = (x.exponent + digits).rem_euclid(n as isize);
191 let shift = n as isize * self.precision as isize - digits + r;
192 let (signif, low, low_digits) = if shift > 0 {
193 (shl_digits::<B>(&xmag, shift as usize), IBig::ZERO, 0)
194 } else {
195 let shift = (-shift) as usize;
196 let (hi, lo) = split_digits_ref::<B>(&xmag, shift);
197 (hi, lo, shift)
198 };
199
200 let mag: UBig = signif.unsigned_abs();
201 let root: UBig = mag.nth_root(n);
202 let rem: UBig = &mag - root.clone().pow(n);
203 let exp = (x.exponent - shift) / n as isize;
204
205 let result_sign = if sign == Sign::Negative {
206 Sign::Negative
207 } else {
208 Sign::Positive
209 };
210 let signed_root: IBig = result_sign * root.clone();
211
212 let res = if rem.is_zero() && low.is_zero() {
213 Approximation::Exact(signed_root)
214 } else {
215 let adjust = R::round_low_part(&signed_root, result_sign, || {
216 // The true value is (mag + low / BASE^low_digits)^(1/n) and
217 // root = floor(mag^(1/n)); its fractional part is compared to 1/2.
218 // frac < 1/2 <=> 2^n * full < (2*root + 1)^n * BASE^low_digits,
219 // where full = mag * BASE^low_digits + low (the full significand).
220 let base_pow = Repr::<B>::BASE.pow(low_digits);
221 let full = &mag * &base_pow + low.unsigned_abs();
222 let lhs = full << n;
223 let rhs = ((root.clone() << 1) + UBig::from_word(1)).pow(n) * base_pow;
224 lhs.cmp(&rhs)
225 });
226 Approximation::Inexact(signed_root.clone() + adjust, adjust)
227 };
228 res.map(|signif| Repr::new(signif, exp))
229 .and_then(|v| self.repr_round(v))
230 .map(|v| FBig::new(v, *self))
231 }
232}