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 <= n {
232                return Repr::one();
233            }
234
235            // Peel off powers of small primes (2, 3, 5, 7) to shrink the
236            // exponent.  Computing x^{n-1} in Newton is prohibitively
237            // expensive for large n, but reducing the exponent step-wise
238            // keeps the intermediate values small.
239            let (repr, remaining) = reduce_by_small_factors(self, n);
240            if remaining == 1 {
241                return repr;
242            }
243            let typed = repr.as_typed();
244            if remaining == 2 {
245                return typed.sqrt();
246            }
247            newton_nth_root(typed, remaining)
248        }
249    }
250
251    /// Strip powers of 2, 3, 5, and 7 from `n`, applying the corresponding
252    /// root operation at each step.  Returns `(intermediate_root, remaining_n)`.
253    fn reduce_by_small_factors(num: TypedReprRef<'_>, mut n: usize) -> (Repr, usize) {
254        // Factor 2 (specialized sqrt)
255        let twos = n.trailing_zeros() as usize;
256        n >>= twos;
257        let mut repr = if twos == 0 {
258            Repr::from_ref(num)
259        } else {
260            let mut r = num.sqrt();
261            for _ in 1..twos {
262                r = r.as_typed().sqrt();
263            }
264            r
265        };
266
267        // Factor 3
268        while n % 3 == 0 {
269            repr = newton_nth_root(repr.as_typed(), 3);
270            n /= 3;
271        }
272        // Factor 5
273        while n % 5 == 0 {
274            repr = newton_nth_root(repr.as_typed(), 5);
275            n /= 5;
276        }
277        // Factor 7
278        while n % 7 == 0 {
279            repr = newton_nth_root(repr.as_typed(), 7);
280            n /= 7;
281        }
282
283        (repr, n)
284    }
285
286    /// Newton's method for the integer nth root of a non-composite n.
287    fn newton_nth_root(num: TypedReprRef<'_>, n: usize) -> Repr {
288        debug_assert!(n > 2);
289        let nm1 = n - 1;
290        let mut guess = UBig::ONE << (num.bit_len() / n); // underestimate
291        let next = |x: &UBig| {
292            let y = UBig(num / x.pow(nm1).into_repr());
293            (y + x * nm1) / n
294        };
295
296        let mut fixpoint = next(&guess);
297        // first go up then go down, to ensure an underestimate
298        while fixpoint > guess {
299            guess = fixpoint;
300            fixpoint = next(&guess);
301        }
302        while fixpoint < guess {
303            guess = fixpoint;
304            fixpoint = next(&guess);
305        }
306        guess.0
307    }
308}