Skip to main content

dashu_int/
root_ops.rs

1use dashu_base::{CubicRoot, CubicRootRem, Sign, SquareRoot, SquareRootRem};
2
3use crate::{
4    error::{panic_root_negative, panic_root_zeroth},
5    ibig::IBig,
6    ubig::UBig,
7};
8
9impl UBig {
10    /// Calculate the nth-root of the integer rounding towards zero.
11    ///
12    /// The result `r` is tight: `r`<sup>`n`</sup> ≤ `self` < `(r + 1)`<sup>`n`</sup>.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// # use dashu_int::UBig;
18    /// assert_eq!(UBig::from(4u8).nth_root(2), UBig::from(2u8));
19    /// assert_eq!(UBig::from(4u8).nth_root(3), UBig::from(1u8));
20    /// assert_eq!(UBig::from(1024u16).nth_root(5), UBig::from(4u8));
21    /// ```
22    ///
23    /// # Panics
24    ///
25    /// If `n` is zero
26    #[inline]
27    pub fn nth_root(&self, n: usize) -> UBig {
28        UBig(self.repr().nth_root(n))
29    }
30}
31
32impl SquareRoot for UBig {
33    type Output = UBig;
34    #[inline]
35    fn sqrt(&self) -> Self::Output {
36        UBig(self.repr().sqrt())
37    }
38}
39
40impl SquareRootRem for UBig {
41    type Output = UBig;
42    #[inline]
43    fn sqrt_rem(&self) -> (Self, Self) {
44        let (s, r) = self.repr().sqrt_rem();
45        (UBig(s), UBig(r))
46    }
47}
48
49impl CubicRoot for UBig {
50    type Output = UBig;
51    #[inline]
52    fn cbrt(&self) -> Self::Output {
53        self.nth_root(3)
54    }
55}
56
57impl CubicRootRem for UBig {
58    type Output = UBig;
59    #[inline]
60    fn cbrt_rem(&self) -> (Self::Output, Self) {
61        let c = self.nth_root(3);
62        let r = self - c.pow(3);
63        (c, r)
64    }
65}
66
67impl IBig {
68    /// Calculate the nth-root of the integer rounding towards zero
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// # use dashu_int::IBig;
74    /// assert_eq!(IBig::from(4).nth_root(2), IBig::from(2));
75    /// assert_eq!(IBig::from(-4).nth_root(3), IBig::from(-1));
76    /// assert_eq!(IBig::from(-1024).nth_root(5), IBig::from(-4));
77    /// ```
78    ///
79    /// # Panics
80    ///
81    /// If `n` is zero, or if `n` is even when the integer is negative.
82    #[inline]
83    pub fn nth_root(&self, n: usize) -> IBig {
84        if n == 0 {
85            panic_root_zeroth()
86        }
87
88        let (sign, mag) = self.as_sign_repr();
89        if sign == Sign::Negative && n % 2 == 0 {
90            panic_root_negative()
91        }
92
93        IBig(mag.nth_root(n).with_sign(sign))
94    }
95}
96
97impl SquareRoot for IBig {
98    type Output = UBig;
99    #[inline]
100    fn sqrt(&self) -> UBig {
101        let (sign, mag) = self.as_sign_repr();
102        if sign == Sign::Negative {
103            panic_root_negative()
104        }
105        UBig(mag.sqrt())
106    }
107}
108
109impl CubicRoot for IBig {
110    type Output = IBig;
111    #[inline]
112    fn cbrt(&self) -> IBig {
113        let (sign, mag) = self.as_sign_repr();
114        if sign == Sign::Negative {
115            panic_root_negative()
116        }
117        IBig(mag.nth_root(3).with_sign(sign))
118    }
119}
120
121mod repr {
122    use super::*;
123    use crate::{
124        add,
125        arch::word::Word,
126        buffer::Buffer,
127        memory::MemoryAllocation,
128        mul,
129        primitive::{extend_word, shrink_dword, WORD_BITS, WORD_BITS_USIZE},
130        repr::{
131            Repr,
132            TypedReprRef::{self, *},
133        },
134        root, shift, shift_ops,
135    };
136    use dashu_base::{SquareRoot, SquareRootRem};
137
138    impl<'a> TypedReprRef<'a> {
139        #[inline]
140        pub fn sqrt(self) -> Repr {
141            match self {
142                RefSmall(dw) => {
143                    if let Some(w) = shrink_dword(dw) {
144                        Repr::from_word(w.sqrt() as Word)
145                    } else {
146                        Repr::from_word(dw.sqrt())
147                    }
148                }
149                RefLarge(words) => sqrt_rem_large(words, true).0,
150            }
151        }
152
153        #[inline]
154        pub fn sqrt_rem(self) -> (Repr, Repr) {
155            match self {
156                RefSmall(dw) => {
157                    if let Some(w) = shrink_dword(dw) {
158                        let (s, r) = w.sqrt_rem();
159                        (Repr::from_word(s as Word), Repr::from_word(r))
160                    } else {
161                        let (s, r) = dw.sqrt_rem();
162                        (Repr::from_word(s), Repr::from_dword(r))
163                    }
164                }
165                RefLarge(words) => sqrt_rem_large(words, false),
166            }
167        }
168    }
169
170    fn sqrt_rem_large(words: &[Word], root_only: bool) -> (Repr, Repr) {
171        // first shift the words so that there are even words and
172        // the top word is normalized. Note: shift <= 2 * WORD_BITS - 2
173        let shift = WORD_BITS_USIZE * (words.len() & 1)
174            + (words.last().unwrap().leading_zeros() & !1) as usize;
175        let n = (words.len() + 1) / 2;
176        let mut buffer = shift_ops::repr::shl_large_ref(words, shift).into_buffer();
177        let mut out = Buffer::allocate(n);
178        out.push_zeros(n);
179
180        let mut allocation = MemoryAllocation::new(root::memory_requirement_sqrt_rem(n));
181        let r_top = root::sqrt_rem(&mut out, &mut buffer, &mut allocation.memory());
182
183        // afterwards, s = out[..], r = buffer[..n] + r_top << n*WORD_BITS
184        // then recover the result if shift != 0
185        if shift != 0 {
186            // to get the final result, let s0 = s mod 2^(shift/2), then
187            // 2^shift*n = (s-s0)^2 + 2s*s0 - s0^2 + r, so final r = (r + 2s*s0 - s0^2) / 2^shift
188            if !root_only {
189                let s0 = out[0] & ((1 << (shift / 2)) - 1);
190                let c1 = mul::add_mul_word_in_place(&mut buffer[..n], 2 * s0, &out);
191                let c2 =
192                    add::sub_dword_in_place(&mut buffer[..n], extend_word(s0) * extend_word(s0));
193                buffer[n] = r_top as Word + c1 - c2 as Word;
194            }
195
196            // s >>= shift/2, r >>= shift
197            let _ = shift::shr_in_place(&mut out, shift as u32 / 2);
198            if !root_only {
199                // Use `>=` (not `>`) so the boundary case `shift == WORD_BITS_USIZE`
200                // also drops a whole word. Otherwise the `shr_in_place(shift %
201                // WORD_BITS) == shr_in_place(0)` below is a no-op and the
202                // division by 2^shift is silently skipped. Hit on 16-bit
203                // Word for inputs like `2^400 - 1` (`words.len() = 25` odd,
204                // top word fully populated, so `shift = WORD_BITS + 0`).
205                if shift >= WORD_BITS_USIZE {
206                    shift::shr_in_place_one_word(&mut buffer);
207                    buffer.truncate(n);
208                } else {
209                    buffer.truncate(n + 1);
210                }
211                let _ = shift::shr_in_place(&mut buffer, shift as u32 % WORD_BITS);
212            }
213        } else if !root_only {
214            buffer[n] = r_top as Word;
215            buffer.truncate(n + 1);
216        }
217
218        (Repr::from_buffer(out), Repr::from_buffer(buffer))
219    }
220
221    impl<'a> TypedReprRef<'a> {
222        pub fn nth_root(self, n: usize) -> Repr {
223            match n {
224                0 => panic_root_zeroth(),
225                1 => return Repr::from_ref(self),
226                2 => return self.sqrt(),
227                _ => {}
228            }
229
230            let bits = self.bit_len();
231            if bits == 0 {
232                return Repr::zero(); // the nth root of 0 is 0, not 1
233            }
234            if bits <= n {
235                return Repr::one();
236            }
237
238            // Peel off powers of small primes (2, 3, 5, 7) to shrink the
239            // exponent.  Computing x^{n-1} in Newton is prohibitively
240            // expensive for large n, but reducing the exponent step-wise
241            // keeps the intermediate values small.
242            let (repr, remaining) = reduce_by_small_factors(self, n);
243            if remaining == 1 {
244                return repr;
245            }
246            let typed = repr.as_typed();
247            if remaining == 2 {
248                return typed.sqrt();
249            }
250            newton_nth_root(typed, remaining)
251        }
252    }
253
254    /// Strip powers of 2, 3, 5, and 7 from `n`, applying the corresponding
255    /// root operation at each step.  Returns `(intermediate_root, remaining_n)`.
256    fn reduce_by_small_factors(num: TypedReprRef<'_>, mut n: usize) -> (Repr, usize) {
257        // Factor 2 (specialized sqrt)
258        let twos = n.trailing_zeros() as usize;
259        n >>= twos;
260        let mut repr = if twos == 0 {
261            Repr::from_ref(num)
262        } else {
263            let mut r = num.sqrt();
264            for _ in 1..twos {
265                r = r.as_typed().sqrt();
266            }
267            r
268        };
269
270        // Factor 3
271        while n % 3 == 0 {
272            repr = newton_nth_root(repr.as_typed(), 3);
273            n /= 3;
274        }
275        // Factor 5
276        while n % 5 == 0 {
277            repr = newton_nth_root(repr.as_typed(), 5);
278            n /= 5;
279        }
280        // Factor 7
281        while n % 7 == 0 {
282            repr = newton_nth_root(repr.as_typed(), 7);
283            n /= 7;
284        }
285
286        (repr, n)
287    }
288
289    /// Newton's method for the integer nth root of a non-composite n.
290    fn newton_nth_root(num: TypedReprRef<'_>, n: usize) -> Repr {
291        debug_assert!(n > 2);
292        let nm1 = n - 1;
293        let mut guess = UBig::ONE << (num.bit_len() / n); // underestimate
294        let next = |x: &UBig| {
295            let y = UBig(num / x.pow(nm1).into_repr());
296            (y + x * nm1) / n
297        };
298
299        let mut fixpoint = next(&guess);
300        // first go up then go down, to ensure an underestimate
301        while fixpoint > guess {
302            guess = fixpoint;
303            fixpoint = next(&guess);
304        }
305        while fixpoint < guess {
306            guess = fixpoint;
307            fixpoint = next(&guess);
308        }
309        guess.0
310    }
311}