#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
use g_math::canonical::{evaluate, CompactShadow, LazyExpr, StackValue};
use g_math::fixed_point::FixedPoint;
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn binary_tier() -> u8 {
match evaluate(&g_math::canonical::gmath("1")).unwrap() {
StackValue::Binary(t, _, _) => t,
v => panic!("expected binary literal, got {v:?}"),
}
}
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn fasc_mul_raw(a: i128, b: i128, tier: u8) -> i128 {
let sa = StackValue::Binary(tier, a as _, CompactShadow::None);
let sb = StackValue::Binary(tier, b as _, CompactShadow::None);
match evaluate(&(LazyExpr::from(sa) * LazyExpr::from(sb))).unwrap() {
StackValue::Binary(_, r, _) => r as i128,
v => panic!("mul left binary domain: {v:?}"),
}
}
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn fasc_div_raw(a: i128, b: i128, tier: u8) -> i128 {
let sa = StackValue::Binary(tier, a as _, CompactShadow::None);
let sb = StackValue::Binary(tier, b as _, CompactShadow::None);
match evaluate(&(LazyExpr::from(sa) / LazyExpr::from(sb))).unwrap() {
StackValue::Binary(_, r, _) => r as i128,
v => panic!("div left binary domain: {v:?}"),
}
}
#[cfg(table_format = "q16_16")]
const FB: u32 = 16; #[cfg(table_format = "q32_32")]
const FB: u32 = 32;
#[cfg(table_format = "q64_64")]
const FB: u32 = 64;
#[cfg(any(table_format = "q128_128", table_format = "q256_256"))]
#[allow(dead_code)]
const FB: u32 = 0;
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn model_mul(a: i128, b: i128, fb: u32) -> i128 {
let wide = a * b; let half = 1i128 << (fb - 1);
let rem = wide & ((1i128 << fb) - 1);
let floor = wide >> fb;
if rem >= half { floor + 1 } else { floor }
}
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn model_div(a: i128, b: i128, fb: u32) -> i128 {
let num = a << fb;
let q = num / b;
let r2 = 2 * (num - q * b).abs();
let babs = b.abs();
let positive = (num < 0) == (b < 0);
if if positive { r2 >= babs } else { r2 > babs } {
q + if positive { 1 } else { -1 }
} else {
q
}
}
#[test]
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn binary_mul_div_path_independent_and_nearest() {
#[cfg(table_format = "q16_16")]
if g_math::fixed_point::frac_config::FRAC_BITS != 16 {
return;
}
let tier = binary_tier();
let mut checked = 0u32;
for i in 0..1500i128 {
for j in 0..40i128 {
let a = i * 331 + 17 + ((i & 1) * -2 + 1) * (j * 7919);
let b = j * 257 + 29 + if i % 3 == 0 { -5000 } else { 4321 };
if b == 0 { continue; }
if FB == 64 && (a.abs() > 1 << 30 || b.abs() > 1 << 30) { continue; }
let imp_mul = (FixedPoint::from_raw(a as _) * FixedPoint::from_raw(b as _)).raw() as i128;
let can_mul = fasc_mul_raw(a, b, tier);
let want_mul = model_mul(a, b, FB);
assert_eq!(imp_mul, want_mul, "imperative mul vs model at ({a},{b})");
assert_eq!(can_mul, want_mul, "canonical mul vs model at ({a},{b})");
let imp_div = (FixedPoint::from_raw(a as _) / FixedPoint::from_raw(b as _)).raw() as i128;
let can_div = fasc_div_raw(a, b, tier);
let want_div = model_div(a, b, FB);
assert_eq!(imp_div, want_div, "imperative div vs model at ({a},{b})");
assert_eq!(can_div, want_div, "canonical div vs model at ({a},{b})");
checked += 1;
}
}
assert!(checked > 10_000, "sweep too small: {checked}");
}
#[test]
#[cfg(any(table_format = "q16_16", table_format = "q32_32", table_format = "q64_64"))]
fn binary_constructed_exact_ties_agree_and_round_up() {
#[cfg(table_format = "q16_16")]
if g_math::fixed_point::frac_config::FRAC_BITS != 16 {
return;
}
let tier = binary_tier();
let half_raw = 1i128 << (FB - 1);
let imp = (FixedPoint::from_raw(1 as _) * FixedPoint::from_raw(half_raw as _)).raw() as i128;
let can = fasc_mul_raw(1, half_raw, tier);
assert_eq!(imp, 1, "positive mul tie must round up (ties toward +∞)");
assert_eq!(can, 1, "canonical must agree on the tie");
let imp_n = (FixedPoint::from_raw(-1i128 as _) * FixedPoint::from_raw(half_raw as _)).raw() as i128;
let can_n = fasc_mul_raw(-1, half_raw, tier);
assert_eq!(imp_n, 0, "negative mul tie must round toward +∞ (to 0)");
assert_eq!(can_n, 0, "canonical must agree on the negative tie");
let two = 2i128 << FB;
assert_eq!((FixedPoint::from_raw(3 as _) / FixedPoint::from_raw(two as _)).raw() as i128, 2);
assert_eq!(fasc_div_raw(3, two, tier), 2);
assert_eq!((FixedPoint::from_raw(-3i128 as _) / FixedPoint::from_raw(two as _)).raw() as i128, -1);
assert_eq!(fasc_div_raw(-3, two, tier), -1);
let a = -3i128;
let b = 4i128 << FB;
assert_eq!((FixedPoint::from_raw(a as _) / FixedPoint::from_raw(b as _)).raw() as i128, -1);
assert_eq!(fasc_div_raw(a, b, tier), -1);
}
#[test]
#[cfg(any(table_format = "q128_128", table_format = "q256_256"))]
fn binary_wide_profiles_nearest_spot_checks() {
let fp = |s: &str| FixedPoint::from_str(s);
assert_eq!((fp("1.5") * fp("1.5")).raw(), fp("2.25").raw());
let tiny = FixedPoint::from_raw({
#[cfg(table_format = "q128_128")]
{ g_math::fixed_point::I256::from_i128(1) }
#[cfg(table_format = "q256_256")]
{ g_math::fixed_point::I512::from_i128(1) }
});
let half = fp("0.5");
let prod = (tiny * half).raw();
let one_raw = {
#[cfg(table_format = "q128_128")]
{ g_math::fixed_point::I256::from_i128(1) }
#[cfg(table_format = "q256_256")]
{ g_math::fixed_point::I512::from_i128(1) }
};
assert_eq!(prod, one_raw, "positive half-ulp product must tie up");
let nprod = ((-tiny) * half).raw();
let zero_raw = {
#[cfg(table_format = "q128_128")]
{ g_math::fixed_point::I256::from_i128(0) }
#[cfg(table_format = "q256_256")]
{ g_math::fixed_point::I512::from_i128(0) }
};
assert_eq!(nprod, zero_raw, "negative half-ulp product must tie toward +∞ (0)");
}