dashu_int/
pow.rs

1//! Exponentiation.
2
3use core::ops::{Shl, Shr};
4
5use crate::{ibig::IBig, ubig::UBig, Sign::*};
6
7impl UBig {
8    /// Raises self to the power of `exp`.
9    ///
10    /// # Examples
11    ///
12    /// ```
13    /// # use dashu_int::UBig;
14    /// assert_eq!(UBig::from(3u8).pow(3), UBig::from(27u8));
15    /// ```
16    #[inline]
17    pub fn pow(&self, exp: usize) -> UBig {
18        // remove factor 2 before actual powering
19        let shift = self.trailing_zeros().unwrap_or(0);
20        let result = if shift != 0 {
21            self.repr()
22                .shr(shift)
23                .as_typed()
24                .pow(exp)
25                .into_typed()
26                .shl(exp * shift)
27        } else {
28            self.repr().pow(exp)
29        };
30        UBig(result)
31    }
32}
33
34impl IBig {
35    /// Raises self to the power of `exp`.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// # use dashu_int::IBig;
41    /// assert_eq!(IBig::from(-3).pow(3), IBig::from(-27));
42    /// ```
43    #[inline]
44    pub fn pow(&self, exp: usize) -> IBig {
45        let (sign, mag) = self.as_sign_repr();
46        let sign = if sign == Negative && exp % 2 == 1 {
47            Negative
48        } else {
49            Positive
50        };
51
52        // remove factor 2 before actual powering
53        let shift = mag.trailing_zeros().unwrap_or(0);
54        let result = if shift != 0 {
55            mag.shr(shift)
56                .as_typed()
57                .pow(exp)
58                .into_typed()
59                .shl(exp * shift)
60        } else {
61            mag.pow(exp)
62        };
63        IBig(result.with_sign(sign))
64    }
65}
66
67// TODO: change the algorithm to right-to-left exponentiation, which should be faster because the exponent has to be
68//       a small integer that fits in a word
69
70pub(crate) mod repr {
71    use dashu_base::DivRem;
72
73    use crate::{
74        arch::word::{DoubleWord, Word},
75        buffer::Buffer,
76        math::{self, bit_len, max_exp_in_word},
77        memory::{self, MemoryAllocation},
78        mul, mul_ops,
79        primitive::{extend_word, shrink_dword, split_dword},
80        repr::{
81            Repr,
82            TypedReprRef::{self, *},
83        },
84        sqr,
85    };
86
87    impl TypedReprRef<'_> {
88        pub fn pow(self, exp: usize) -> Repr {
89            // shortcuts
90            match exp {
91                0 => return Repr::one(),
92                1 => return Repr::from_ref(self),
93                2 => return self.sqr(),
94                _ => {}
95            };
96
97            match self {
98                RefSmall(dword) => {
99                    if let Some(word) = shrink_dword(dword) {
100                        pow_word_base(word, exp)
101                    } else {
102                        pow_dword_base(dword, exp)
103                    }
104                }
105                RefLarge(words) => pow_large_base(words, exp),
106            }
107        }
108    }
109
110    pub(crate) fn pow_word_base(base: Word, exp: usize) -> Repr {
111        debug_assert!(exp > 1);
112        match base {
113            0 => return Repr::zero(),
114            1 => return Repr::one(),
115            2 => return Repr::zero().into_typed().set_bit(exp),
116            b if b.is_power_of_two() => {
117                return Repr::zero()
118                    .into_typed()
119                    .set_bit(exp * base.trailing_zeros() as usize)
120            }
121            _ => {}
122        }
123
124        // lift the base to a full word and some shortcuts
125        let (wexp, wbase) = max_exp_in_word(base);
126        if exp < wexp {
127            return Repr::from_word(base.pow(exp as u32));
128        } else if exp < 2 * wexp {
129            let pow = base.pow((exp - wexp) as u32);
130            return Repr::from_dword(extend_word(wbase) * extend_word(pow));
131        }
132
133        // by now wexp / exp >= 2, result = wbase ^ (wexp / exp) * base ^ (wexp % exp)
134        let (exp, exp_rem) = exp.div_rem(wexp);
135        let mut res = Buffer::allocate(exp + 1); // result is at most exp + 1 words
136        let mut allocation = MemoryAllocation::new(
137            memory::add_layout(
138                memory::array_layout::<Word>(exp / 2 + 1), // store res before squaring
139                sqr::memory_requirement_exact(exp / 2 + 1),
140            ), // memory for squaring
141        );
142        let mut memory = allocation.memory();
143
144        // res = wbase * wbase
145        let mut p = bit_len(exp) - 2;
146        let (lo, hi) = split_dword(extend_word(wbase) * extend_word(wbase));
147        res.push(lo);
148        res.push(hi);
149
150        loop {
151            if exp & (1 << p) != 0 {
152                let carry = mul::mul_word_in_place(&mut res, wbase);
153                res.push_resizing(carry); // actually never resize
154            }
155            if p == 0 {
156                break;
157            }
158            p -= 1;
159
160            // res = square(res)
161            let (tmp, mut memory) = memory.allocate_slice_copy(&res);
162            res.fill(0);
163            res.push_zeros(res.len());
164            sqr::sqr(&mut res, tmp, &mut memory);
165        }
166
167        // carry out the remaining multiplications
168        let pow_rem = base.pow(exp_rem as u32);
169        let carry = mul::mul_word_in_place(&mut res, pow_rem);
170        res.push_resizing(carry);
171        Repr::from_buffer(res)
172    }
173
174    pub(crate) fn pow_dword_base(base: DoubleWord, exp: usize) -> Repr {
175        debug_assert!(exp > 1);
176        debug_assert!(base > Word::MAX as DoubleWord);
177
178        let mut res = Buffer::allocate(2 * exp); // result is at most 2 * exp words
179        let mut allocation = MemoryAllocation::new(
180            memory::add_layout(
181                memory::array_layout::<Word>(exp), // store res before squaring
182                sqr::memory_requirement_exact(exp),
183            ), // memory for squaring
184        );
185        let mut memory = allocation.memory();
186
187        // res = base * base
188        let mut p = bit_len(exp) - 2;
189        let (lo, hi) = math::mul_add_carry_dword(base, base, 0);
190        let (n0, n1) = split_dword(lo);
191        res.push(n0);
192        res.push(n1);
193        let (n2, n3) = split_dword(hi);
194        res.push(n2);
195        res.push(n3);
196
197        loop {
198            if exp & (1 << p) != 0 {
199                let carry = mul::mul_dword_in_place(&mut res, base);
200                if carry > 0 {
201                    let (c0, c1) = split_dword(carry);
202                    res.push(c0);
203                    res.push_resizing(c1); // actually never resize
204                }
205            }
206            if p == 0 {
207                break;
208            }
209            p -= 1;
210
211            // res = square(res)
212            let (tmp, mut memory) = memory.allocate_slice_copy(&res);
213            res.fill(0);
214            res.push_zeros(res.len());
215            sqr::sqr(&mut res, tmp, &mut memory);
216        }
217
218        Repr::from_buffer(res)
219    }
220
221    pub(crate) fn pow_large_base(base: &[Word], exp: usize) -> Repr {
222        debug_assert!(exp > 1);
223        let mut p = bit_len(exp) - 2;
224        let mut res = mul_ops::repr::square_large(base);
225        loop {
226            if exp & (1 << p) != 0 {
227                res = mul_ops::repr::mul_large(res.as_slice(), base);
228            }
229            if p == 0 {
230                break;
231            }
232            p -= 1;
233            res = mul_ops::repr::square_large(res.as_slice());
234        }
235        res
236    }
237}