use crate::fixed::Q0_16;
#[must_use]
pub const fn quantize<const BITS: u32>(v: Q0_16) -> (u16, i32) {
const { assert!(BITS >= 1 && BITS <= 16, "BITS must be in 1..=16") };
let shift = 16u32.saturating_sub(BITS);
let q = match v.to_raw().checked_shr(shift) {
Some(code) => code,
None => 0,
};
let residual = (v.to_raw() as i32).saturating_sub(expand::<BITS>(q).to_raw() as i32);
(q, residual)
}
#[must_use]
pub const fn expand<const BITS: u32>(q: u16) -> Q0_16 {
const { assert!(BITS >= 1 && BITS <= 16, "BITS must be in 1..=16") };
let shift = 16u32.saturating_sub(BITS);
let code = match 1u16.checked_shl(BITS) {
None => q,
Some(width) => q & width.saturating_sub(1),
};
match code.checked_shl(shift) {
Some(v) => Q0_16::from_raw(v),
None => Q0_16::ZERO,
}
}
#[cfg(test)]
mod tests {
use super::{Q0_16, expand, quantize};
fn identity_holds<const BITS: u32>(v: u16) {
let (q, residual) = quantize::<BITS>(Q0_16::from_raw(v));
let reconstructed = (expand::<BITS>(q).to_raw() as i32).saturating_add(residual);
assert_eq!(reconstructed, i32::from(v), "BITS={BITS} v={v}");
}
#[test]
fn edges_identity() {
identity_holds::<1>(0);
identity_holds::<1>(1);
identity_holds::<1>(65535);
identity_holds::<8>(0);
identity_holds::<8>(1);
identity_holds::<8>(65535);
identity_holds::<16>(0);
identity_holds::<16>(1);
identity_holds::<16>(65535);
}
#[test]
fn bits16_is_identity_with_zero_residual() {
assert_eq!(quantize::<16>(Q0_16::from_raw(0)), (0, 0));
assert_eq!(quantize::<16>(Q0_16::from_raw(1)), (1, 0));
assert_eq!(quantize::<16>(Q0_16::from_raw(65535)), (65535, 0));
assert_eq!(expand::<16>(0xABCD), Q0_16::from_raw(0xABCD));
}
#[test]
fn truncates_toward_zero_without_rounding() {
let (q, residual) = quantize::<8>(Q0_16::from_raw(0x8080));
assert_eq!(q, 0x80);
assert_eq!(residual, 0x80);
assert_eq!(expand::<8>(q), Q0_16::from_raw(0x8000));
assert_ne!(q, 0x81);
}
#[test]
fn expand_uses_only_the_bits_wide_code() {
assert_eq!(expand::<8>(0x80), Q0_16::from_raw(0x8000));
assert_eq!(expand::<8>(0x80FF), Q0_16::from_raw(0xFF00));
}
#[test]
fn exhaustive_identity_all_valid_bits() {
for v in 0..=u16::MAX {
identity_holds::<1>(v);
identity_holds::<2>(v);
identity_holds::<3>(v);
identity_holds::<4>(v);
identity_holds::<5>(v);
identity_holds::<6>(v);
identity_holds::<7>(v);
identity_holds::<8>(v);
identity_holds::<9>(v);
identity_holds::<10>(v);
identity_holds::<11>(v);
identity_holds::<12>(v);
identity_holds::<13>(v);
identity_holds::<14>(v);
identity_holds::<15>(v);
identity_holds::<16>(v);
}
}
}