decimal-scaled 0.4.4

Const-generic base-10 fixed-point decimals (D9/D18/D38/D76/D153/D307) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly.
Documentation
//! D38 floating-point power kernel — `powf` computed as
//! `exp(y · ln(x))` on the 256-bit `Fixed` intermediate.
//!
//! Width-level specialisation for D38, capturing the hand-tuned path
//! that has shipped since before the algorithm library existed. The
//! `ln`, the multiplication, and the `exp` all share the wide guard-
//! digit working scale so no precision is dropped between stages.
//!
//! Fast path preserved verbatim from the typed surface:
//! - A non-positive base saturates to `0` (matches the f64-bridge
//!   NaN-to-ZERO policy for negative bases with arbitrary fractional
//!   exponents).
//! - Integer-valued exponents with `|n| <= INT_FAST_PATH_THRESHOLD`
//!   route to `D38::<SCALE>::powi(n)` — exact via square-and-multiply,
//!   ~10–500× faster than the `exp(y·ln(x))` chain.
//!
//! Returns the raw `i128` storage at the input's scale.

use crate::algos::exp::fixed_d38::exp_fixed;
use crate::algos::ln::fixed_d38::{STRICT_GUARD, ln_fixed};
use crate::algos::fixed_d38::Fixed;
use crate::support::rounding::RoundingMode;
use crate::types::widths::D38;

/// Integer-exponent fast-path threshold for `powf_strict`.
///
/// At `|n| <= 64`, the square-and-multiply `powi(n)` costs at most
/// ~12 multiplications (2·log2(64)) — comfortably cheaper than the
/// `exp(y · ln(x))` chain (one `ln_fixed` + one `mul` + one
/// `exp_fixed`, each ~hundreds of i128 ops on the 256-bit `Fixed`
/// intermediate). Above 64 the integer path's cost grows
/// logarithmically while the transcendental path's cost is constant,
/// so a fixed threshold is sufficient.
pub(crate) const INT_FAST_PATH_THRESHOLD: i32 = 64;

/// Returns `Some(n)` if `exp_raw` (at `SCALE`) represents an exact
/// integer value `n` that fits `i32` and `|n| <= INT_FAST_PATH_THRESHOLD`.
#[inline]
fn exp_as_small_int<const SCALE: u32>(exp_raw: i128) -> Option<i32> {
    let mult = 10_i128.pow(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
    }
}

/// `base^exp` with caller-chosen `working_digits` above the storage scale.
///
/// Both `base` and `exp` are raw storage at `scale`. Non-positive `base`
/// saturates to `0`.
#[inline]
#[must_use]
pub(crate) fn powf_with<const SCALE: u32>(
    base: i128,
    exp: i128,
    working_digits: u32,
    mode: RoundingMode,
) -> i128 {
    if base <= 0 {
        return 0;
    }
    if let Some(n) = exp_as_small_int::<SCALE>(exp) {
        return D38::<SCALE>::from_bits(base).powi(n).to_bits();
    }
    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 };
    exp_fixed(y_w.mul(ln_x, w), w)
        .round_to_i128_with(w, SCALE, mode)
        .unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("powf kernel", SCALE))
}

/// Strict variant — const-folded `working_digits = STRICT_GUARD`.
#[inline]
#[must_use]
pub(crate) fn powf_strict<const SCALE: u32>(
    base: i128,
    exp: i128,
    mode: RoundingMode,
) -> i128 {
    if base <= 0 {
        return 0;
    }
    if let Some(n) = exp_as_small_int::<SCALE>(exp) {
        return D38::<SCALE>::from_bits(base).powi(n).to_bits();
    }
    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 };
    exp_fixed(y_w.mul(ln_x, w), w)
        .round_to_i128_with(w, SCALE, mode)
        .unwrap_or_else(|| crate::support::diagnostics::overflow_panic_with_scale("powf kernel", SCALE))
}