use crate::int::types::traits::BigInt;
use crate::support::rounding::{should_bump, RoundingMode};
pub(crate) enum ExactPin<V> {
Value(V),
OutOfRange,
Defer,
}
#[inline]
pub(crate) fn powi_exact_pin<St: BigInt, const SCALE: u32>(
base: St,
exp: St,
storage_max: St,
mode: RoundingMode,
) -> Option<St> {
match powi_exact_pin_checked::<St, SCALE>(base, exp, storage_max, mode) {
ExactPin::Value(v) => Some(v),
ExactPin::OutOfRange => {
crate::support::diagnostics::overflow_panic_with_scale("powf kernel", SCALE)
}
ExactPin::Defer => None,
}
}
#[inline]
pub(crate) fn powi_exact_pin_checked<St: BigInt, const SCALE: u32>(
base: St,
exp: St,
storage_max: St,
mode: RoundingMode,
) -> ExactPin<St> {
let one_s = crate::consts::pow10::dispatch::<St>(SCALE);
let (n_big, e_rem) = exp.div_rem(one_s);
if e_rem != St::ZERO {
return ExactPin::Defer;
}
let thresh =
St::from_i128(crate::algos::pow::powf_series_2limb::INT_FAST_PATH_THRESHOLD as i128);
if n_big > thresh || n_big < St::ZERO - thresh {
return ExactPin::Defer;
}
let n = n_big.to_i128() as i32;
if base <= St::ZERO {
return ExactPin::Defer;
}
let (bv, b_rem) = base.div_rem(one_s);
if b_rem != St::ZERO {
return ExactPin::Defer; }
if n == 0 || bv == St::ONE {
return ExactPin::Value(one_s);
}
let k = n.unsigned_abs();
if n > 0 {
match bv
.checked_pow(k)
.and_then(|p| p.checked_mul(one_s))
.filter(|v| *v <= storage_max)
{
Some(v) => ExactPin::Value(v),
None => ExactPin::OutOfRange,
}
} else {
match bv.checked_pow(k) {
Some(d) => {
let (q, r) = one_s.div_rem(d);
if r == St::ZERO {
return ExactPin::Value(q); }
let cmp = r.cmp(&(d - r));
let q_is_odd = q.bit(0);
let bump = should_bump(mode, cmp, q_is_odd, true);
ExactPin::Value(if bump { q + St::ONE } else { q })
}
None => {
let bump = should_bump(mode, core::cmp::Ordering::Less, false, true);
ExactPin::Value(if bump { St::ONE } else { St::ZERO })
}
}
}
}
pub(crate) fn exp_as_small_int_raw<St: BigInt, const SCALE: u32>(exp: St) -> Option<i32> {
let one_s = crate::consts::pow10::dispatch::<St>(SCALE);
let (n_big, e_rem) = exp.div_rem(one_s);
if e_rem != St::ZERO {
return None;
}
let thresh =
St::from_i128(crate::algos::pow::powf_series_2limb::INT_FAST_PATH_THRESHOLD as i128);
if n_big > thresh || n_big < St::ZERO - thresh {
return None;
}
Some(n_big.to_i128() as i32)
}
fn checked_pow10<St: BigInt>(d: u32) -> Option<St> {
let ten = St::from_i128(10);
let mut v = St::ONE;
for _ in 0..d {
v = v.checked_mul(ten)?;
}
Some(v)
}
enum Pow10Div<St> {
Q(St, St),
OutOfRange,
Wide,
}
fn div_pow10_small<St: BigInt>(e: u32, d: St, storage_max: St) -> Pow10Div<St> {
let ten = St::from_i128(10);
let (mut q, mut r) = St::ONE.div_rem(d);
for _ in 0..e {
let r10 = match r.checked_mul(ten) {
Some(v) => v,
None => return Pow10Div::Wide,
};
let (digit, rr) = r10.div_rem(d); q = match q.checked_mul(ten).and_then(|v| v.checked_add(digit)) {
Some(v) => v,
None => return Pow10Div::OutOfRange,
};
if q > storage_max {
return Pow10Div::OutOfRange;
}
r = rr;
}
Pow10Div::Q(q, r)
}
pub(crate) fn powi_terminating_pin<St: BigInt, const SCALE: u32>(
base: St,
n: i32,
storage_max: St,
mode: RoundingMode,
) -> Option<St> {
debug_assert!(n != 0);
if base <= St::ZERO {
return None;
}
let ten = St::from_i128(10);
let mut m = base;
let mut z = 0u32;
loop {
let (q, r) = m.div_rem(ten);
if r != St::ZERO || z >= SCALE {
break;
}
m = q;
z += 1;
}
let f = SCALE - z;
let k = n.unsigned_abs();
let mk = m.checked_pow(k)?; let fk = f.checked_mul(k)?;
let place = |q: St, r: St, d: St| -> St {
if r == St::ZERO {
return q;
}
let cmp = r.cmp(&(d - r));
let bump = should_bump(mode, cmp, q.bit(0), true);
if bump {
q + St::ONE
} else {
q
}
};
let v = if n > 0 {
if fk <= SCALE {
mk.checked_mul(checked_pow10::<St>(SCALE - fk)?)?
} else {
match checked_pow10::<St>(fk - SCALE) {
Some(d) => {
let (q, r) = mk.div_rem(d);
place(q, r, d)
}
None => {
let bump = should_bump(mode, core::cmp::Ordering::Less, false, true);
if bump {
St::ONE
} else {
St::ZERO
}
}
}
}
} else {
let e = SCALE.checked_add(fk)?;
match checked_pow10::<St>(e) {
Some(num) => {
let (q, r) = num.div_rem(mk);
place(q, r, mk)
}
None => match div_pow10_small::<St>(e, mk, storage_max) {
Pow10Div::Q(q, r) => place(q, r, mk),
Pow10Div::OutOfRange => crate::support::diagnostics::overflow_panic_with_scale(
"powf kernel",
SCALE,
),
Pow10Div::Wide => return None,
},
}
};
if v > storage_max {
crate::support::diagnostics::overflow_panic_with_scale("powf kernel", SCALE)
}
Some(v)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::int::types::Int;
#[test]
fn terminating_pin_exact_ties_and_reciprocals() {
let pin = |base: i128, n: i32, mode: RoundingMode| {
powi_terminating_pin::<Int<2>, 2>(Int::<2>::from_i128(base), n, Int::<2>::MAX, mode)
.map(|v| v.as_i128())
};
for mode in MODES {
assert_eq!(pin(250, 2, mode), Some(625), "{mode:?} 2.5^2");
assert_eq!(pin(50, -2, mode), Some(400), "{mode:?} 0.5^-2");
}
assert_eq!(pin(150, 3, RoundingMode::HalfToEven), Some(338)); assert_eq!(pin(150, 3, RoundingMode::HalfTowardZero), Some(337));
assert_eq!(pin(150, 3, RoundingMode::Trunc), Some(337));
assert_eq!(pin(150, 3, RoundingMode::Ceiling), Some(338));
let pin37 = |base: i128, n: i32, mode: RoundingMode| {
powi_terminating_pin::<Int<2>, 37>(Int::<2>::from_i128(base), n, Int::<2>::MAX, mode)
.map(|v| v.as_i128())
};
let half_raw = 5 * 10_i128.pow(36);
for mode in MODES {
assert_eq!(pin37(half_raw, -2, mode), Some(4 * 10_i128.pow(37)), "{mode:?} 0.5^-2 @37");
}
assert_eq!(pin(150, -1, RoundingMode::Floor), Some(66));
assert_eq!(pin(150, -1, RoundingMode::HalfToEven), Some(67));
assert_eq!(pin(150, -1, RoundingMode::Ceiling), Some(67));
}
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Trunc,
RoundingMode::Floor,
RoundingMode::Ceiling,
];
fn pin_raw<const SC: u32>(base_raw: i128, exp_raw: i128, mode: RoundingMode) -> Option<i128> {
powi_exact_pin::<Int<2>, SC>(
Int::<2>::from_i128(base_raw),
Int::<2>::from_i128(exp_raw),
Int::<2>::MAX,
mode,
)
.map(|v| v.as_i128())
}
fn pin<const SC: u32>(base: i128, exp: i128, mode: RoundingMode) -> Option<i128> {
let one = 10_i128.pow(SC);
pin_raw::<SC>(base * one, exp * one, mode)
}
#[track_caller]
fn check_exact<const SC: u32>(base: i128, exp: i128, divisor: i128) {
let one = 10_i128.pow(SC);
for mode in MODES {
assert_eq!(
pin::<SC>(base, exp, mode),
Some(one / divisor),
"base={base} exp={exp} scale={SC} mode={mode:?}"
);
}
}
#[test]
fn exact_reciprocals_are_mode_independent() {
check_exact::<37>(10, -2, 100); check_exact::<37>(16, -2, 256); check_exact::<37>(4, -3, 64); check_exact::<37>(5, -3, 125); check_exact::<36>(20, -2, 400); check_exact::<36>(25, -2, 625); check_exact::<36>(25, -3, 15_625); }
#[test]
fn inexact_reciprocal_rounds_each_direction() {
let q = 10_i128.pow(37) / 3; assert_eq!(pin::<37>(3, -1, RoundingMode::Floor), Some(q));
assert_eq!(pin::<37>(3, -1, RoundingMode::Trunc), Some(q));
assert_eq!(pin::<37>(3, -1, RoundingMode::HalfToEven), Some(q));
assert_eq!(pin::<37>(3, -1, RoundingMode::Ceiling), Some(q + 1));
}
#[test]
fn positive_powers_are_exact_integers() {
let one = 10_i128.pow(37);
for mode in MODES {
assert_eq!(pin::<37>(2, 3, mode), Some(8 * one));
assert_eq!(pin::<37>(17, 1, mode), Some(17 * one)); }
}
#[test]
#[should_panic(expected = "powf kernel")]
fn positive_overflow_panics() {
let _ = pin::<37>(10, 2, RoundingMode::HalfToEven);
}
#[test]
fn non_integer_base_or_exp_defers() {
let one = 10_i128.pow(37);
assert_eq!(pin_raw::<37>(one * 5 / 2, -2 * one, RoundingMode::Floor), None);
assert_eq!(pin_raw::<37>(2 * one, one / 2, RoundingMode::Floor), None);
}
}