use crate::math::Fix128;
pub const EXP_OVERFLOW_SENTINEL: Fix128 = Fix128 {
hi: i64::MAX >> 32,
lo: 0,
};
#[must_use]
pub fn exp_fix(x: Fix128) -> Fix128 {
let sat_hi = Fix128::from_int(20);
let sat_lo = Fix128::from_int(-40);
if x >= sat_hi {
return EXP_OVERFLOW_SENTINEL;
}
if x <= sat_lo {
return Fix128::ZERO;
}
let half = Fix128::from_ratio(1, 2);
let neg_half = Fix128::from_ratio(-1, 2);
let mut y = x;
let mut shifts: u32 = 0;
while y > half || y < neg_half {
y = y.half();
shifts += 1;
}
let mut term = Fix128::ONE;
let mut sum = Fix128::ONE;
for k in 1..=12u32 {
term = term * y / Fix128::from_int(i64::from(k));
sum = sum + term;
}
for _ in 0..shifts {
sum = sum * sum;
}
sum
}
#[must_use]
pub fn pow_int(x: Fix128, n: i32) -> Fix128 {
if n == 0 {
return Fix128::ONE;
}
let mut result = Fix128::ONE;
let abs_n = n.unsigned_abs();
for _ in 0..abs_n {
result = result * x;
}
if n < 0 {
if result.is_zero() {
return Fix128::ZERO;
}
Fix128::ONE / result
} else {
result
}
}
#[must_use]
pub fn cbrt_fix(n: Fix128) -> Fix128 {
if n <= Fix128::ZERO {
return Fix128::ZERO;
}
let mut x = if n >= Fix128::ONE {
Fix128::from_int(1 + n.hi.max(1) / 2)
} else {
Fix128::from_ratio(5, 10)
};
for _ in 0..24 {
let x_sq = x * x;
if x_sq.is_zero() {
break;
}
let new = (x.double() + n / x_sq) / Fix128::from_int(3);
x = new;
}
x
}
#[inline]
#[must_use]
pub fn clamp_fix(x: Fix128, lo: Fix128, hi: Fix128) -> Fix128 {
if x < lo {
lo
} else if x > hi {
hi
} else {
x
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: Fix128, b: Fix128, tol: Fix128) -> bool {
let d = if a > b { a - b } else { b - a };
d <= tol
}
#[test]
fn exp_zero_is_one() {
assert_eq!(exp_fix(Fix128::ZERO), Fix128::ONE);
}
#[test]
fn exp_one_e() {
let e = exp_fix(Fix128::ONE);
let expected = Fix128::from_ratio(271_828, 100_000);
assert!(approx_eq(e, expected, Fix128::from_ratio(1, 1000)));
}
#[test]
fn exp_neg_one_is_inv_e() {
let v = exp_fix(Fix128::NEG_ONE);
let expected = Fix128::from_ratio(3679, 10_000);
assert!(approx_eq(v, expected, Fix128::from_ratio(1, 1000)));
}
#[test]
fn exp_saturates_high() {
let v = exp_fix(Fix128::from_int(1000));
assert_eq!(v, EXP_OVERFLOW_SENTINEL);
}
#[test]
fn exp_saturates_low_to_zero() {
let v = exp_fix(Fix128::from_int(-1000));
assert_eq!(v, Fix128::ZERO);
}
#[test]
fn pow_int_zero_exp_is_one() {
assert_eq!(pow_int(Fix128::from_int(5), 0), Fix128::ONE);
}
#[test]
fn pow_int_positive() {
assert_eq!(pow_int(Fix128::from_int(2), 5), Fix128::from_int(32));
}
#[test]
fn pow_int_negative_reciprocal() {
let v = pow_int(Fix128::from_int(2), -3);
assert_eq!(v, Fix128::from_ratio(125, 1000));
}
#[test]
fn pow_int_zero_negative_returns_zero() {
let v = pow_int(Fix128::ZERO, -3);
assert_eq!(v, Fix128::ZERO);
}
#[test]
fn clamp_below_returns_lo() {
let v = clamp_fix(Fix128::from_int(-5), Fix128::ZERO, Fix128::from_int(10));
assert_eq!(v, Fix128::ZERO);
}
#[test]
fn clamp_above_returns_hi() {
let v = clamp_fix(Fix128::from_int(20), Fix128::ZERO, Fix128::from_int(10));
assert_eq!(v, Fix128::from_int(10));
}
#[test]
fn cbrt_zero_is_zero() {
assert_eq!(cbrt_fix(Fix128::ZERO), Fix128::ZERO);
}
#[test]
fn cbrt_negative_returns_zero() {
assert_eq!(cbrt_fix(Fix128::from_int(-8)), Fix128::ZERO);
}
#[test]
fn cbrt_eight_is_two() {
let v = cbrt_fix(Fix128::from_int(8));
assert!(approx_eq(
v,
Fix128::from_int(2),
Fix128::from_ratio(1, 100)
));
}
#[test]
fn cbrt_twenty_seven_is_three() {
let v = cbrt_fix(Fix128::from_int(27));
assert!(approx_eq(
v,
Fix128::from_int(3),
Fix128::from_ratio(1, 100)
));
}
#[test]
fn cbrt_one_is_one() {
let v = cbrt_fix(Fix128::ONE);
assert!(approx_eq(v, Fix128::ONE, Fix128::from_ratio(1, 100)));
}
#[test]
fn cbrt_fractional() {
let v = cbrt_fix(Fix128::from_ratio(125, 1000));
assert!(approx_eq(
v,
Fix128::from_ratio(5, 10),
Fix128::from_ratio(1, 100)
));
}
#[test]
fn clamp_inside_returns_value() {
let v = clamp_fix(Fix128::from_int(5), Fix128::ZERO, Fix128::from_int(10));
assert_eq!(v, Fix128::from_int(5));
}
}