bigdecimal/arithmetic/
pow.rs

1//! pow implementation
2
3use stdlib::num::NonZeroU64;
4
5use crate::*;
6
7use super::log10;
8use super::multiplication::{
9    multiply_decimals_with_context,
10    multiply_slices_with_prec_into_p19_z,
11};
12
13use bigdigit::digitvec::{DigitVec, DigitSlice};
14use bigdigit::radix::{RadixType, RadixPowerOfTen, RADIX_u64, RADIX_10p19_u64};
15use bigdigit::endian::{Endianness, LittleEndian};
16
17use arithmetic::multiplication::mul_scaled_slices_truncating_into;
18
19
20/// Compute bd**exp using exponentiation by squaring algorithm, while maintaining the
21/// precision specified in ctx (the number of digits would otherwise explode).
22///
23/// Algorithm comes from https://en.wikipedia.org/wiki/Exponentiation_by_squaring
24pub(crate) fn impl_powi_with_context<'a>(
25    bd: impl Into<BigDecimalRef<'a>>,
26    exp: i64,
27    ctx: &Context,
28) -> BigDecimal {
29    powi_with_context(bd.into(), exp, ctx)
30}
31
32/// Calculate BigDecimalRef `bd` to power of signed integer, using context
33/// to set precision
34fn powi_with_context(
35    bd: BigDecimalRef,
36    exp: i64,
37    ctx: &Context,
38) -> BigDecimal {
39    match exp.cmp(&0) {
40        Ordering::Equal => BigDecimal::one(),
41        Ordering::Greater => pow_u64_with_context(bd, exp as u64, ctx),
42        Ordering::Less => {
43            if exp == -1 {
44                return bd.inverse_with_context(&ctx);
45            }
46            // create truncating "double wide" context for inverting
47            let inv_ctx = Context::new_truncating(
48                (ctx.precision().get() * 2)
49                .max((bd.digits.bits() as f64 * LOG10_2 * 2.0).ceil() as u64)
50                .max(38) // always at least 2 u64 'big-digits' wide
51            );
52            let inverted_bd = bd.inverse_with_context(&inv_ctx);
53            // requested context when taking the power of inverted value
54            pow_u64_with_context(inverted_bd.to_ref(), unsigned_abs(exp), &ctx)
55        }
56    }
57}
58
59
60/// Calculate BigDecimalRef `bd` to power of positive integer, using context
61/// to set precision
62///
63/// The case where exp==0 (therefore result is 1) must be handled
64/// before calling this pow impl
65fn pow_u64_with_context(
66    bd: BigDecimalRef,
67    exp: u64,
68    ctx: &Context,
69) -> BigDecimal {
70    type R = RADIX_10p19_u64;
71
72    if true {
    match (&exp, &0) {
        (left_val, right_val) => {
            if *left_val == *right_val {
                let kind = ::core::panicking::AssertKind::Ne;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    };
};debug_assert_ne!(exp, 0);
73
74    if exp == 1 {
75        return ctx.round_decimal_ref(bd);
76    }
77
78    // bd^exp guaranteed to fit within precision: use roundless pow
79    if (bd.digits.bits() as f64 * exp as f64) < (ctx.precision().get() as f64 * LOG2_10) {
80        return pow_u64_no_context(bd, exp);
81    }
82
83    let mut tmp = Vec::new();
84    let bd_as_base10p19 = DigitVec::from_biguint_using_tmp(bd.digits, &mut tmp);
85    if true {
    match (&tmp.len(), &0) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    };
};debug_assert_eq!(tmp.len(), 0);
86
87    let mut prec = RunningPrecision::new(
88        bd_as_base10p19.as_digit_slice(),
89        NonZeroU64::new(exp).unwrap(),
90        ctx.precision(),
91    );
92
93    // Count the number of multiplications we're going to perform, one per "1" binary digit
94    // in exp, and the number of times we can divide exp by 2.
95    let mut n = exp;
96
97    // factor product into bdⁿ => bdˣ·bdʸ where x is largest
98    // power of two which fits into 'n'
99    let final_x_pow = 1 << (63 - n.leading_zeros() as u64);
100    let final_y_pow = n - final_x_pow;
101
102    // if final_y_pow == 0, then 'y' digits is never used and
103    // we will use x*x as final multiplication, otherwise
104    // final product is x*y
105    let final_x_times_y = final_y_pow != 0;
106
107    let mut y_vec = Vec::new();
108    if final_x_times_y {
109        y_vec.reserve(bd_as_base10p19.len());
110        y_vec.push(1);
111    } else {
112        // no final multiplication by y, so decrease number of squarings
113        // by one so we do (x/2 * x/2) rather than (x * 1)
114        n >>= 1;
115    }
116
117    let mut digits_x = WithScale {
118        value: bd_as_base10p19,
119        scale: 0,
120    };
121    let mut digits_y = WithScale {
122        value: DigitVec::from_vec(y_vec),
123        scale: 0,
124    };
125    // temporary storage for results of multiplications
126    let mut prod = WithScale {
127        value: DigitVec::from_vec(tmp),
128        scale: 0,
129    };
130
131    // tracks if skipped insignificant digits are zero for final rounding
132    let mut trailing_zeros = true;
133
134    while n > 1 {
135        if n % 2 == 1 {
136            // 'prod' is now product y * x, swap with 'y'
137            let skipped_bigdigit_count = mul_scaled_slices_truncating_into(
138                &mut prod,
139                digits_y.as_digit_slice(),
140                digits_x.as_digit_slice(),
141                prec.next(),
142            );
143
144            trailing_zeros = trailing_zeros
145                && {
146                    let skipped = skipped_bigdigit_count.saturating_sub(digits_y.value.len() - 1);
147                    digits_x.value.least_n_are_zero(skipped)
148                }
149                && {
150                    let skipped = skipped_bigdigit_count.saturating_sub(digits_x.value.len() - 1);
151                    digits_y.value.least_n_are_zero(skipped)
152                };
153
154            // now 'digits_y <- prod = digits_y * digits_x'
155            stdlib::mem::swap(&mut prod, &mut digits_y);
156        }
157        // TODO: optimized algorithm for squaring a scaled digitslice to requested precision
158        let skipped_bigdigits = mul_scaled_slices_truncating_into(
159            &mut prod,
160            digits_x.as_digit_slice(),
161            digits_x.as_digit_slice(),
162            prec.next(),
163        );
164        // detect if truncated digits were non-zero
165        trailing_zeros = trailing_zeros
166            && {
167                let skipped = skipped_bigdigits.saturating_sub(digits_x.value.len() - 1);
168                digits_x.value.least_n_are_zero(skipped)
169            };
170
171        // digits_x <- prod = digits_x * digits_x
172        stdlib::mem::swap(&mut prod, &mut digits_x);
173
174        // shift lowest bit out of multiplication counter
175        n >>= 1;
176    }
177
178    let sign = if exp % 2 == 0 {
179        Sign::Plus
180    } else {
181        bd.sign()
182    };
183    let rounding = crate::rounding::NonDigitRoundingData {
184        mode: ctx.rounding_mode(),
185        sign: sign,
186    };
187
188    prod.value.clear();
189    prod.scale = 0;
190
191    let x_slice = digits_x.value.as_digit_slice();
192
193    let y_slice = if final_x_times_y {
194        digits_y.value.as_digit_slice()
195    } else {
196        // raised to a power-of-two: y-slice was never touched so
197        // we reuse x-slice here for final multiplication
198        if true {
    match (&digits_y.value.digits.capacity(), &0) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    };
};debug_assert_eq!(digits_y.value.digits.capacity(), 0);
199        digits_y.scale = digits_x.scale;
200        x_slice
201    };
202
203    multiply_slices_with_prec_into_p19_z(
204        &mut prod,
205        x_slice,
206        y_slice,
207        ctx.precision(),
208        rounding,
209        trailing_zeros,
210    );
211
212    let mut scale = bd.scale * exp as i64 + prod.scale;
213    scale += (digits_x.scale + digits_y.scale) * R::DIGITS as i64;
214
215    let int_val = BigInt::from_biguint(sign, prod.value.into_biguint());
216
217    BigDecimal::new(int_val, scale)
218}
219
220/// Simple implementation of exponentiation-by-squaring,
221/// with no precision/rounding involved
222fn pow_u64_no_context(bd: BigDecimalRef, exp: u64) -> BigDecimal {
223    if true {
    match (&exp, &0) {
        (left_val, right_val) => {
            if *left_val == *right_val {
                let kind = ::core::panicking::AssertKind::Ne;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    };
};debug_assert_ne!(exp, 0);
224    if exp == 1 {
225        return bd.to_owned();
226    }
227
228    let mut x = bd.digits.clone();
229    let mut y: BigUint = 1u8.into();
230
231    let mut n = exp;
232    while n > 1 {
233        if n % 2 == 1 {
234            y *= &x;
235        }
236        x = x.pow(2u8);
237        n >>= 1;
238    }
239
240    // final product
241    let p = x * y;
242
243    let sign = if exp % 2 == 0 {
244        Sign::Plus
245    } else {
246        bd.sign()
247    };
248
249    let scale = bd.scale * exp as i64;
250    let int_val = BigInt::from_biguint(sign, p);
251
252    BigDecimal::new(int_val, scale)
253}
254
255#[cfg(not(has_unsigned_abs))]
256fn unsigned_abs(n: i64) -> u64 {
257    if n != i64::MIN {
258        n.abs() as u64
259    } else {
260        (i64::MIN as i128).abs() as u64
261    }
262}
263
264#[cfg(has_unsigned_abs)]
265#[allow(clippy::incompatible_msrv)]
266fn unsigned_abs(n: i64) -> u64 {
267    n.unsigned_abs()
268}
269
270/// Struct housing the 'margin' information for calculating the required
271/// precision while doing sequential multiplications for pow
272///
273/// Currently uses a naive scheme: calculating the widest required
274/// margin, and multiplying the number of multiplications by that width.
275/// Then we linearly decrease the margin so we end up near the requested
276/// precision by the time we get to the final product.
277///
278struct RunningPrecision {
279    /// Minimum precision
280    min: u64,
281    /// Current margin
282    margin: u64,
283    /// number of digits to decrease each time `next()` is called
284    margin_per_mul: u64,
285}
286
287impl RunningPrecision {
288    /// Create from requiring 'prec' digits of precision of digits^exp
289    fn new<'a, R: RadixPowerOfTen, E: Endianness>(
290        digits: DigitSlice<'a, R, E>,
291        exp: NonZeroU64,
292        prec: NonZeroU64,
293    ) -> Self {
294        // number of big-digits required to fit requested precision, plus a few
295        // extra for guaranteed rounding digits
296        let prec_bigdigit_count = R::divceil_digit_count(prec.get() as usize + 3) as u64;
297
298        // length of 'digits' in big digits (floating-point)
299        let digit_count_f = digits.count_decimal_digits() as f64 ;
300
301        let count_squarings = 63 - exp.get().leading_zeros();
302        let count_non_squarings = exp.get().count_ones() - 1;
303        // total number of multiplications
304        let muls = (count_non_squarings + count_squarings) as u64;
305
306        // aⁿ => aˣ·aʸ, n = x+y
307        let max_partial_pow = {
308            let x = 1 << count_squarings;
309            let y = exp.get() - x;
310            (x / 2).max(y)
311        };
312
313        // number of digits of multiplicand in the final product
314        let max_width_digits_f = digit_count_f * max_partial_pow as f64;
315
316        // length in digits of kmaximum sum of digits
317        let diag_sum_digit_len = 2.0 * log10(R::max().to_f64().unwrap()) + log10(max_width_digits_f);
318        let diag_sum_bigdigit_len = R::divceil_digit_count(diag_sum_digit_len.ceil() as usize) as u64;
319        let margin_per_mul = diag_sum_bigdigit_len + 1;
320
321        Self {
322            min: prec_bigdigit_count,
323            margin: (muls + 1) * margin_per_mul,
324            margin_per_mul: margin_per_mul,
325        }
326    }
327
328    /// update margin and return precision
329    fn next(&mut self) -> u64 {
330        self.margin = self.margin.saturating_sub(self.margin_per_mul);
331        self.margin + self.min
332    }
333}
334
335#[cfg(test)]
336mod test {
337    use super::*;
338
339    include!("pow.tests.rs");
340}