use super::decoder_tables::{INV_SQRT_TABLE, LOG2_TABLE, POW2_TABLE, SQRT_L_TABLE};
use crate::fixed_point::arith::{add, extract_h, extract_l, sub};
use crate::fixed_point::arith32::{l_deposit_h, l_msu, l_mult};
use crate::fixed_point::shift::{l_shl, l_shr, l_shr_r, norm_l, shr};
use crate::fixed_point::types::{DspContext, Word16, Word32};
fn interpolate(ctx: &mut DspContext, table: &[i16], index: usize, a: Word16) -> Word32 {
let l_y = l_deposit_h(Word16(table[index]));
let tmp = sub(ctx, Word16(table[index]), Word16(table[index + 1]));
l_msu(ctx, l_y, tmp, a)
}
fn index_and_fraction(ctx: &mut DspContext, l_x: Word32) -> (i16, Word16) {
let i = extract_h(l_x);
let l_x = l_shr(ctx, l_x, 1);
let a = extract_l(l_x);
(i.0, Word16(a.0 & 0x7fff))
}
pub fn log2_norm(ctx: &mut DspContext, l_x: Word32, exp: i16) -> (Word16, Word16) {
if l_x.0 <= 0 {
return (Word16(0), Word16(0));
}
let exponent = sub(ctx, Word16(30), Word16(exp));
let positioned = l_shr(ctx, l_x, 9);
let (i, a) = index_and_fraction(ctx, positioned);
let i = usize::try_from(i - 32).expect("log2 index is normalised into range");
let l_y = interpolate(ctx, &LOG2_TABLE, i, a);
(exponent, extract_h(l_y))
}
pub fn log2(ctx: &mut DspContext, l_x: Word32) -> (Word16, Word16) {
let exp = norm_l(l_x);
let normalised = l_shl(ctx, l_x, exp);
log2_norm(ctx, normalised, exp)
}
pub fn pow2(ctx: &mut DspContext, exponent: Word16, fraction: Word16) -> Word32 {
let l_x = l_mult(ctx, fraction, Word16(32));
let (i, a) = index_and_fraction(ctx, l_x);
let i = usize::try_from(i).expect("pow2 index is non-negative");
let l_x = interpolate(ctx, &POW2_TABLE, i, a);
let exp = sub(ctx, Word16(30), exponent);
l_shr_r(ctx, l_x, exp.0)
}
pub fn sqrt_l_exp(ctx: &mut DspContext, l_x: Word32) -> (Word32, i16) {
if l_x.0 <= 0 {
return (Word32(0), 0);
}
let e = norm_l(l_x) & !1;
let l_x = l_shl(ctx, l_x, e);
let positioned = l_shr(ctx, l_x, 9);
let (i, a) = index_and_fraction(ctx, positioned);
let i = usize::try_from(i - 16).expect("sqrt index is normalised into range");
(interpolate(ctx, &SQRT_L_TABLE, i, a), e)
}
pub fn inv_sqrt(ctx: &mut DspContext, l_x: Word32) -> Word32 {
if l_x.0 <= 0 {
return Word32(0x3fff_ffff);
}
let norm = norm_l(l_x);
let mut l_x = l_shl(ctx, l_x, norm);
let mut exp = sub(ctx, Word16(30), Word16(norm));
if (exp.0 & 1) == 0 {
l_x = l_shr(ctx, l_x, 1);
}
exp = shr(ctx, exp, 1);
exp = add(ctx, exp, Word16(1));
let positioned = l_shr(ctx, l_x, 9);
let (i, a) = index_and_fraction(ctx, positioned);
let i = usize::try_from(i - 16).expect("inv_sqrt index is normalised into range");
let l_y = interpolate(ctx, &INV_SQRT_TABLE, i, a);
l_shr(ctx, l_y, exp.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> DspContext {
DspContext::default()
}
#[test]
fn log2_recovers_the_exponent_of_a_power_of_two() {
let mut c = ctx();
for shift in 1..=30i16 {
let (exponent, fraction) = log2(&mut c, Word32(1 << shift));
assert_eq!(exponent.0, shift, "log2(2^{shift})");
assert_eq!(fraction.0, 0, "2^{shift} has no fractional log2");
}
}
#[test]
fn log2_is_monotonic_and_tracks_the_real_logarithm() {
let mut c = ctx();
let mut last = f64::MIN;
for x in (1..=0x3fff_ffffi32).step_by(0x0010_0000) {
let (e, f) = log2(&mut c, Word32(x));
let got = f64::from(e.0) + f64::from(f.0) / 32768.0;
let want = f64::from(x).log2();
assert!((got - want).abs() < 0.001, "log2({x}) = {got}, want {want}");
assert!(got > last, "log2 must increase; {got} followed {last}");
last = got;
}
}
#[test]
fn pow2_inverts_log2() {
let mut c = ctx();
for &x in &[3i32, 100, 12_345, 1_000_000, 0x3fff_ffff] {
let (exponent, fraction) = log2(&mut c, Word32(x));
let back = pow2(&mut c, exponent, fraction);
let rel = (f64::from(back.0) - f64::from(x)).abs() / f64::from(x);
assert!(rel < 0.001, "2^log2({x}) = {} (relative {rel})", back.0);
}
}
#[test]
fn sqrt_squares_back_to_its_input() {
let mut c = ctx();
for &x in &[1i32 << 20, 1 << 25, 12_345_678, 0x3fff_ffff] {
let (y, e) = sqrt_l_exp(&mut c, Word32(x));
let root = f64::from(y.0) / 2f64.powi(31) / 2f64.powi(i32::from(e) / 2);
let want = f64::from(x) / 2f64.powi(31);
assert!(
(root * root - want).abs() / want < 0.001,
"sqrt({x})^2 = {} , want {want}",
root * root
);
}
}
#[test]
fn inv_sqrt_is_the_reciprocal_of_the_integer_root() {
let mut c = ctx();
for &x in &[1i32 << 20, 1 << 25, 12_345_678, 0x3fff_ffff] {
let got = f64::from(inv_sqrt(&mut c, Word32(x)).0) / 2f64.powi(30);
let want = 1.0 / f64::from(x).sqrt();
assert!(
(got - want).abs() / want < 0.001,
"inv_sqrt({x}) = {got}, want {want}"
);
}
}
#[test]
fn degenerate_inputs_return_the_references_stand_ins() {
let mut c = ctx();
assert_eq!(log2(&mut c, Word32(0)), (Word16(0), Word16(0)));
assert_eq!(log2(&mut c, Word32(-1)), (Word16(0), Word16(0)));
assert_eq!(sqrt_l_exp(&mut c, Word32(0)), (Word32(0), 0));
assert_eq!(inv_sqrt(&mut c, Word32(0)).0, 0x3fff_ffff);
}
}