use crate::algos::exp::exp_generic as eg;
use crate::algos::exp::exp_series_2limb::exp_fixed;
use crate::algos::support::fixed::Fixed;
use crate::algos::support::narrow_ziv::{self, WZiv};
use crate::algos::ln::ln_series_2limb::{STRICT_GUARD, ln_fixed};
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
pub(crate) const INT_FAST_PATH_THRESHOLD: i32 = 64;
#[inline]
fn powf_overflow_gate(arg: Fixed, w: u32, scale: u32) -> bool {
if arg.negative || w < 6 {
return false; }
let thr = Fixed {
negative: false,
mag: Fixed::pow10(w - 6),
}
.mul_u128(((39 - scale) as u128) * 2_302_586);
arg.ge_mag(thr)
}
#[inline]
fn mul_div_scale_checked<const SCALE: u32>(a: i128, b: i128, mode: RoundingMode) -> Option<i128> {
crate::algos::support::mg_divide::mul_div_pow10_with::<SCALE>(a, b, mode)
}
#[inline]
fn powi_raw_checked<const SCALE: u32>(base: i128, n: i32, mode: RoundingMode) -> Option<i128> {
let one_s: Int<2> = const { crate::consts::pow10::dispatch_int::<2>(SCALE) };
if n == 0 {
return Some(one_s.as_i128());
}
let pos_n = n.unsigned_abs();
let mut acc: i128 = one_s.as_i128();
let mut b: i128 = base;
let mut e = pos_n;
while e > 0 {
if e & 1 == 1 {
acc = mul_div_scale_checked::<SCALE>(acc, b, mode)?;
}
e >>= 1;
if e > 0 {
b = mul_div_scale_checked::<SCALE>(b, b, mode)?;
}
}
if n > 0 {
Some(acc)
} else {
Some(
crate::algos::div::div_widen_scale::div_widen_scale::<2>(
one_s,
Int::<2>::from_i128(acc),
const { crate::consts::pow10::dispatch_int::<2>(SCALE) },
mode,
)
.as_i128(),
)
}
}
#[inline]
fn exp_as_small_int<const SCALE: u32>(exp_raw: i128) -> Option<i32> {
let mult = const { crate::consts::pow10::dispatch_i128(SCALE) };
if exp_raw % mult != 0 {
return None;
}
let q = exp_raw / mult;
if !(i32::MIN as i128..=i32::MAX as i128).contains(&q) {
return None;
}
let n = q as i32;
if n.unsigned_abs() <= INT_FAST_PATH_THRESHOLD as u32 {
Some(n)
} else {
None
}
}
#[inline]
#[must_use]
pub(crate) fn powf_with<const SCALE: u32>(
base: Int<2>,
exp: Int<2>,
working_digits: u32,
mode: RoundingMode,
) -> Option<Int<2>> {
powf_with_raw::<SCALE>(base.as_i128(), exp.as_i128(), working_digits, mode)
.map(Int::<2>::from_i128)
}
#[inline]
fn powf_with_raw<const SCALE: u32>(
base: i128,
exp: i128,
working_digits: u32,
mode: RoundingMode,
) -> Option<i128> {
if base <= 0 {
return Some(0);
}
if let Some(n) = exp_as_small_int::<SCALE>(exp) {
if let Some(v) = powi_raw_checked::<SCALE>(base, n, mode) {
return Some(v);
}
use crate::algos::pow::powi_exact::ExactPin;
match crate::algos::pow::powi_exact::powi_exact_pin_checked::<Int<2>, SCALE>(
Int::<2>::from_i128(base),
Int::<2>::from_i128(exp),
Int::<2>::MAX,
mode,
) {
ExactPin::Value(v) => return Some(v.as_i128()),
ExactPin::OutOfRange => return None,
ExactPin::Defer => {}
}
}
let w = SCALE + working_digits;
let pow = 10u128.pow(working_digits);
let ln_x = ln_fixed(Fixed::from_u128_mag(base as u128, false).mul_u128(pow), w);
let y_neg = exp < 0;
let y_w = Fixed::from_u128_mag(exp.unsigned_abs(), false).mul_u128(pow);
let y_w = if y_neg { y_w.neg() } else { y_w };
let arg = y_w.mul(ln_x, w);
if powf_overflow_gate(arg, w, SCALE) {
return None;
}
exp_fixed(arg, w).round_to_i128_with(w, SCALE, mode)
}
#[inline]
#[must_use]
pub(crate) fn powf_strict<const SCALE: u32>(base: Int<2>, exp: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
powf_strict_raw::<SCALE>(base.as_i128(), exp.as_i128(), mode).map(Int::<2>::from_i128)
}
#[inline]
fn powf_strict_raw<const SCALE: u32>(base: i128, exp: i128, mode: RoundingMode) -> Option<i128> {
if base <= 0 {
return Some(0);
}
if let Some(n) = exp_as_small_int::<SCALE>(exp) {
if let Some(v) = powi_raw_checked::<SCALE>(base, n, mode) {
return Some(v);
}
use crate::algos::pow::powi_exact::ExactPin;
match crate::algos::pow::powi_exact::powi_exact_pin_checked::<Int<2>, SCALE>(
Int::<2>::from_i128(base),
Int::<2>::from_i128(exp),
Int::<2>::MAX,
mode,
) {
ExactPin::Value(v) => return Some(v.as_i128()),
ExactPin::OutOfRange => return None,
ExactPin::Defer => {}
}
}
let w = SCALE + STRICT_GUARD;
let pow = 10u128.pow(STRICT_GUARD);
let ln_x = ln_fixed(Fixed::from_u128_mag(base as u128, false).mul_u128(pow), w);
let y_neg = exp < 0;
let y_w = Fixed::from_u128_mag(exp.unsigned_abs(), false).mul_u128(pow);
let y_w = if y_neg { y_w.neg() } else { y_w };
let arg = y_w.mul(ln_x, w);
if powf_overflow_gate(arg, w, SCALE) {
return None;
}
let v = exp_fixed(arg, w);
match v.round_to_i128_clear_of_tie(w, SCALE, mode) {
Some(r) => r,
None => {
if let Some(num2) =
v.double().round_to_i128_with(w, SCALE, RoundingMode::HalfToEven)
{
if let Some(pinned) = powf_rational_pin(base, exp, SCALE, num2) {
return Some(pinned);
}
}
narrow_ziv::walk_checked(
v.round_to_i128_with(w, SCALE, mode),
STRICT_GUARD,
SCALE,
mode,
|g| powf_ziv(base, exp, SCALE, g),
)
}
}
}
fn powf_ziv(base: i128, exp: i128, scale: u32, g: u32) -> WZiv {
let w = scale + g;
let ln_x = eg::ln_fixed::<WZiv>(narrow_ziv::lift(base, g), w, narrow_ziv::ln2_w(w));
let arg = eg::mul::<WZiv>(narrow_ziv::lift(exp, g), ln_x, w);
eg::exp_fixed::<WZiv>(arg, w)
}
fn powf_rational_pin(base: i128, exp: i128, scale: u32, num2: i128) -> Option<i128> {
use crate::algos::ln::ln_series_2limb::{gcd_u128, pow_bounded, reduce_fraction};
if num2 <= 0 || num2 & 1 == 1 || exp == 0 || base <= 0 {
return None;
}
let one_s = 10u128.pow(scale);
let y_neg = exp < 0;
let g = gcd_u128(exp.unsigned_abs(), one_s);
let p = exp.unsigned_abs() / g;
let q = one_s / g;
let (xn, xd) = reduce_fraction(base as u128, one_s);
let (vn, vd) = reduce_fraction((num2 / 2) as u128, one_s);
let (tn, td) = if y_neg { (xd, xn) } else { (xn, xd) };
let lx_n = pow_bounded(tn, p)?;
let lx_d = pow_bounded(td, p)?;
let rv_n = pow_bounded(vn, q)?;
let rv_d = pow_bounded(vd, q)?;
if lx_n != rv_n || lx_d != rv_d {
return None;
}
Some(num2 / 2)
}
#[cfg(test)]
mod tests {
use super::*;
const S: u32 = 37;
const ONE: i128 = 10_i128.pow(S);
const M: RoundingMode = RoundingMode::HalfToEven;
const MODES: [RoundingMode; 6] = [
RoundingMode::HalfToEven,
RoundingMode::HalfAwayFromZero,
RoundingMode::HalfTowardZero,
RoundingMode::Trunc,
RoundingMode::Floor,
RoundingMode::Ceiling,
];
fn powf(base: i128, exp: i128) -> i128 {
powf_strict_raw::<S>(base * ONE, exp * ONE, M).expect("in range")
}
#[test]
fn in_range_reciprocal_with_overflowing_intermediate_computes() {
assert_eq!(powf(10, -2), ONE / 100);
assert_eq!(powf(5, -3), ONE / 125); assert_eq!(powf(16, -2), ONE / 256); }
#[track_caller]
fn check_directed_exact<const SC: u32>(base: i128, exp: i128, divisor: i128) {
let one = 10_i128.pow(SC);
for mode in MODES {
assert_eq!(
powf_strict_raw::<SC>(base * one, exp * one, mode),
Some(one / divisor),
"base={base} exp={exp} scale={SC} mode={mode:?}"
);
}
}
#[test]
fn overflowing_intermediate_reciprocal_is_directed_exact() {
check_directed_exact::<37>(10, -2, 100); check_directed_exact::<37>(16, -2, 256); check_directed_exact::<37>(4, -3, 64); check_directed_exact::<37>(5, -3, 125); check_directed_exact::<36>(20, -2, 400); check_directed_exact::<36>(25, -2, 625); check_directed_exact::<36>(25, -3, 15_625); }
#[test]
fn exact_small_powers_unchanged() {
assert_eq!(powf(2, 3), 8 * ONE);
assert_eq!(powf(2, -3), ONE / 8); assert_eq!(powf(4, -2), ONE / 16); assert_eq!(powf(17, 1), 17 * ONE); }
#[test]
fn sqrt_like_exact_powers_are_directed_exact() {
let one19 = 10_i128.pow(19);
let half = 5 * 10_i128.pow(18); for mode in MODES {
assert_eq!(
powf_strict_raw::<19>(4 * one19, half, mode),
Some(2 * one19),
"powf(4, 0.5) mode={mode:?}"
);
assert_eq!(
powf_strict_raw::<19>(225 * one19, half, mode),
Some(15 * one19),
"powf(225, 0.5) mode={mode:?}"
);
assert_eq!(
powf_strict_raw::<19>(225 * one19 / 100, half, mode),
Some(15 * one19 / 10),
"powf(2.25, 0.5) mode={mode:?}"
);
}
}
#[test]
fn out_of_range_positive_power_signals_none() {
assert_eq!(powf_strict_raw::<S>(10 * ONE, 2 * ONE, M), None);
}
}