use crate::*;
use super::*;
pub(crate) fn impl_exp(n: BigDecimalRef, ctx: &Context) -> BigDecimal {
use arithmetic::division::scaled_uint_division_into;
use arithmetic::inverse::impl_inverse_uint_scale;
if n.is_zero() {
return BigDecimal::one();
}
let (x, k) = factor_two_to_k_scale(WithScale { value: n.digits, scale: n.scale });
let target_precision = ctx.precision().get();
let target_precision_bits = digit_to_bit_count(target_precision).max(64 * 3) + k as u64;
let mut num = x.clone();
let mut den = WithScale { value: 1u8.into(), scale: 0 };
let mut sum = WithScale { value: 1u8.into(), scale: 0 };
addition::addassign_scaled_biguint(&mut sum, num.as_ref());
debug_assert_eq!(sum.count_int_digits(), 1);
let mut delta: WithScale<BigUint> = Default::default();
let stop = (target_precision * 2).max(10);
for i in 2..stop {
num.mulassign_scaled_biguint(&x);
den.value *= i;
remove_trailing_zeros(&mut den, &[1]);
scaled_uint_division_into(&mut delta, &num, &den, target_precision_bits - i);
sum.addassign_scaled_biguint(&delta);
let leading_zero_count = delta.count_int_digits().neg();
if leading_zero_count > target_precision as i64 {
break;
}
}
let mut tmp = delta.value;
arithmetic::pow::pow_2_k_scaled_biguint(
&mut sum, &mut tmp, k, target_precision_bits
);
let result = BigDecimal::from(sum);
if n.sign == Sign::Minus {
return ctx.invert(&result);
} else {
return ctx.round_decimal(result);
}
}
fn factor_two_to_k_scale(n: WithScale<&BigUint>) -> (WithScale<BigUint>, u16) {
let log2_n = n.value.bits() as f64;
let log2_s = (n.scale as f64) * LOG2_10;
let k = 1.0 + log2_n - log2_s;
if k <= 0.5 {
let r = n.value.clone();
return ((r, n.scale).into(), 0);
}
let k = k.ceil() as u64;
let mut x = BigUint::from(5u8).pow(k);
x *= n.value;
let mut result = WithScale {
value: x,
scale: n.scale + k as i64,
};
remove_trailing_zeros(&mut result, &[8, 1]);
(result, k.as_())
}
fn remove_trailing_zeros<'a>(
n: &mut WithScale<BigUint>,
powers: impl IntoIterator<Item=&'a u8>,
) {
for &i in powers.into_iter() {
debug_assert!(i < 20);
let s = 10u64.pow(i as u32);
while (&n.value % s).is_zero() {
n.value /= s;
n.scale -= i as i64;
}
}
}
#[cfg(test)]
mod test {
use super::*;
include!("exp.tests.rs");
}