#![allow(clippy::module_name_repetitions)]
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
pub struct Q16(pub i32);
impl Q16 {
pub const ZERO: Q16 = Q16(0);
pub const ONE: Q16 = Q16(1 << 16);
pub const MIN: Q16 = Q16(i32::MIN);
pub const MAX: Q16 = Q16(i32::MAX);
#[must_use]
pub const fn from_int(x: i16) -> Q16 {
Q16((x as i32) << 16)
}
#[must_use]
pub const fn from_raw(raw: i32) -> Q16 {
Q16(raw)
}
#[must_use]
pub const fn raw(self) -> i32 {
self.0
}
#[must_use]
pub const fn sat_add(self, b: Q16) -> Q16 {
Q16(self.0.saturating_add(b.0))
}
#[must_use]
pub const fn sat_sub(self, b: Q16) -> Q16 {
Q16(self.0.saturating_sub(b.0))
}
#[must_use]
pub const fn sat_mul(self, b: Q16) -> Q16 {
let prod: i64 = (self.0 as i64) * (b.0 as i64);
let rounded: i64 = round_half_even_i64_shift(prod, 16);
Q16(saturate_i64_to_i32(rounded))
}
#[must_use]
pub const fn sat_div(self, b: Q16) -> Q16 {
if b.0 == 0 {
return Q16::ZERO;
}
let num: i64 = (self.0 as i64) << 16;
Q16(saturate_i64_to_i32(num / (b.0 as i64)))
}
#[must_use]
pub const fn abs(self) -> Q16 {
Q16(self.0.saturating_abs())
}
#[must_use]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn lerp(self, other: Q16, alpha: Q16) -> Q16 {
let diff = other.sat_sub(self);
let step = alpha.sat_mul(diff);
self.sat_add(step)
}
}
#[must_use]
const fn saturate_i64_to_i32(x: i64) -> i32 {
if x > i32::MAX as i64 {
i32::MAX
} else if x < i32::MIN as i64 {
i32::MIN
} else {
x as i32
}
}
#[must_use]
const fn round_half_even_i64_shift(value: i64, bits: u32) -> i64 {
if bits == 0 {
return value;
}
let truncated: i64 = value >> bits;
let halfway_bit: i64 = 1i64 << (bits - 1);
let discarded: i64 = value & ((1i64 << bits) - 1);
let remainder_below_half: i64 = discarded & (halfway_bit - 1);
let at_half: bool = discarded == halfway_bit && remainder_below_half == 0;
if at_half {
if truncated & 1 == 0 {
truncated
} else {
truncated.saturating_add(1)
}
} else if discarded > halfway_bit {
truncated.saturating_add(1)
} else {
truncated
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_have_expected_raw_bits() {
assert_eq!(Q16::ZERO.raw(), 0);
assert_eq!(Q16::ONE.raw(), 1 << 16);
assert_eq!(Q16::MIN.raw(), i32::MIN);
assert_eq!(Q16::MAX.raw(), i32::MAX);
}
#[test]
fn from_int_round_trips_through_integer_part() {
for x in [-32_767_i16, -1, 0, 1, 32_767] {
let q = Q16::from_int(x);
assert_eq!(q.raw() >> 16, i32::from(x));
assert_eq!(q.raw() & 0xFFFF, 0);
}
}
#[test]
fn sat_add_clamps_at_max() {
assert_eq!(Q16::MAX.sat_add(Q16::ONE), Q16::MAX);
assert_eq!(Q16::MIN.sat_add(Q16::from_int(-1)), Q16::MIN);
assert_eq!(Q16::from_int(2).sat_add(Q16::from_int(3)), Q16::from_int(5));
}
#[test]
fn sat_sub_clamps_at_min() {
assert_eq!(Q16::MIN.sat_sub(Q16::ONE), Q16::MIN);
assert_eq!(Q16::MAX.sat_sub(Q16::from_int(-1)), Q16::MAX);
assert_eq!(Q16::from_int(5).sat_sub(Q16::from_int(3)), Q16::from_int(2));
}
#[test]
fn sat_mul_is_one_identity() {
for x in [-100, -1, 0, 1, 100, 32_767] {
let q = Q16::from_int(x);
assert_eq!(q.sat_mul(Q16::ONE), q);
assert_eq!(Q16::ONE.sat_mul(q), q);
}
}
#[test]
fn sat_mul_half_times_half_is_quarter() {
let half = Q16::from_raw(0x8000);
let quarter = Q16::from_raw(0x4000);
assert_eq!(half.sat_mul(half), quarter);
}
#[test]
fn sat_mul_handles_negative_signs() {
let a = Q16::from_int(-3);
let b = Q16::from_int(4);
assert_eq!(a.sat_mul(b), Q16::from_int(-12));
assert_eq!(b.sat_mul(a), Q16::from_int(-12));
assert_eq!(a.sat_mul(a), Q16::from_int(9));
}
#[test]
fn sat_mul_saturates_on_overflow() {
let big = Q16::from_int(30_000);
assert_eq!(big.sat_mul(big), Q16::MAX);
assert_eq!(big.sat_mul(Q16::from_int(-30_000)), Q16::MIN);
}
#[test]
fn sat_div_one_is_identity() {
for x in [-100, -1, 0, 1, 100] {
let q = Q16::from_int(x);
assert_eq!(q.sat_div(Q16::ONE), q);
}
}
#[test]
fn sat_div_by_zero_returns_zero() {
assert_eq!(Q16::from_int(5).sat_div(Q16::ZERO), Q16::ZERO);
assert_eq!(Q16::from_int(-5).sat_div(Q16::ZERO), Q16::ZERO);
}
#[test]
fn abs_saturates_at_min() {
assert_eq!(Q16::MIN.abs(), Q16::MAX);
assert_eq!(Q16::from_int(-7).abs(), Q16::from_int(7));
assert_eq!(Q16::from_int(7).abs(), Q16::from_int(7));
}
#[test]
fn lerp_at_zero_returns_self() {
let a = Q16::from_int(10);
let b = Q16::from_int(20);
assert_eq!(a.lerp(b, Q16::ZERO), a);
}
#[test]
fn lerp_at_one_returns_other() {
let a = Q16::from_int(10);
let b = Q16::from_int(20);
assert_eq!(a.lerp(b, Q16::ONE), b);
}
#[test]
fn lerp_at_half_returns_midpoint() {
let a = Q16::from_int(10);
let b = Q16::from_int(20);
let half = Q16::from_raw(0x8000);
assert_eq!(a.lerp(b, half), Q16::from_int(15));
}
#[test]
fn ewma_recurrence_with_locked_alpha_is_stable() {
let alpha = Q16::from_raw(0x2000);
let x = Q16::from_int(100);
let mut ewma = Q16::ZERO;
let mut last = Q16::MIN;
for _ in 0..200 {
ewma = ewma.lerp(x, alpha);
assert!(ewma.raw() >= last.raw());
assert!(ewma.raw() <= x.raw());
last = ewma;
}
let final_gap = x.sat_sub(ewma).raw();
assert!(
final_gap <= 4,
"expected gap ≤ 4 raw units, got {final_gap}"
);
}
#[test]
fn round_half_even_breaks_ties_to_even() {
let cases: [(i64, i64); 4] = [
(0b0_1000, 0), (0b1_1000, 2), (0b10_1000, 2), (0b11_1000, 4), ];
for (input, expected) in cases {
assert_eq!(
round_half_even_i64_shift(input, 4),
expected,
"input={input:#b}"
);
}
}
#[test]
fn round_half_even_handles_negative_midpoints() {
let cases: [(i64, i64); 4] = [
(-(0b0_1000_i64), 0),
(-(0b1_1000_i64), -2),
(-(0b10_1000_i64), -2),
(-(0b11_1000_i64), -4),
];
for (input, expected) in cases {
assert_eq!(
round_half_even_i64_shift(input, 4),
expected,
"input={input}"
);
}
}
}