use core::cmp::Ordering;
pub trait LlrScalar: Copy + Default + core::fmt::Debug {
type Wide: Copy + Default;
const ZERO: Self;
const POS_INF_LIKE: Self;
fn from_f32(x: f32) -> Self;
fn to_f32(self) -> f32;
fn neg_sat(self) -> Self;
fn abs_sat(self) -> Self;
fn is_negative(self) -> bool;
fn cmp_total(self, other: Self) -> Ordering;
#[inline]
fn lt_total(self, other: Self) -> bool {
matches!(self.cmp_total(other), Ordering::Less)
}
fn mul_alpha(self, alpha: f32) -> Self;
fn to_wide(self) -> Self::Wide;
fn wide_zero() -> Self::Wide;
fn wide_add(a: Self::Wide, b: Self::Wide) -> Self::Wide;
fn wide_sub(a: Self::Wide, b: Self::Wide) -> Self::Wide;
fn from_wide_sat(w: Self::Wide) -> Self;
fn wide_is_positive(w: Self::Wide) -> bool;
}
impl LlrScalar for f32 {
type Wide = f32;
const ZERO: f32 = 0.0;
const POS_INF_LIKE: f32 = f32::INFINITY;
#[inline]
fn from_f32(x: f32) -> Self {
x
}
#[inline]
fn to_f32(self) -> f32 {
self
}
#[inline]
fn neg_sat(self) -> Self {
-self
}
#[inline]
fn abs_sat(self) -> Self {
#[cfg(feature = "std")]
{
self.abs()
}
#[cfg(not(feature = "std"))]
{
use num_traits::Float;
Float::abs(self)
}
}
#[inline]
fn is_negative(self) -> bool {
self < 0.0
}
#[inline]
fn cmp_total(self, other: Self) -> Ordering {
self.partial_cmp(&other).unwrap_or(Ordering::Equal)
}
#[inline]
fn mul_alpha(self, alpha: f32) -> Self {
self * alpha
}
#[inline]
fn to_wide(self) -> Self::Wide {
self
}
#[inline]
fn wide_zero() -> Self::Wide {
0.0
}
#[inline]
fn wide_add(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a + b
}
#[inline]
fn wide_sub(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a - b
}
#[inline]
fn from_wide_sat(w: Self::Wide) -> Self {
w
}
#[inline]
fn wide_is_positive(w: Self::Wide) -> bool {
w > 0.0
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct Q11i16(pub i16);
const Q11_FRAC: u32 = 11;
const Q11_ONE: i32 = 1 << Q11_FRAC;
impl LlrScalar for Q11i16 {
type Wide = i32;
const ZERO: Q11i16 = Q11i16(0);
const POS_INF_LIKE: Q11i16 = Q11i16(i16::MAX);
#[inline]
fn from_f32(x: f32) -> Self {
let v = (x * Q11_ONE as f32) as i32;
Q11i16(v.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
}
#[inline]
fn to_f32(self) -> f32 {
(self.0 as f32) / (Q11_ONE as f32)
}
#[inline]
fn neg_sat(self) -> Self {
Q11i16(self.0.checked_neg().unwrap_or(i16::MAX))
}
#[inline]
fn abs_sat(self) -> Self {
Q11i16(self.0.saturating_abs())
}
#[inline]
fn is_negative(self) -> bool {
self.0 < 0
}
#[inline]
fn cmp_total(self, other: Self) -> Ordering {
self.0.cmp(&other.0)
}
#[inline]
fn mul_alpha(self, alpha: f32) -> Self {
let aq15 = (alpha * 32768.0) as i32;
let prod = (self.0 as i32) * aq15;
let v = prod >> 15;
Q11i16(v.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
}
#[inline]
fn to_wide(self) -> Self::Wide {
self.0 as i32
}
#[inline]
fn wide_zero() -> Self::Wide {
0
}
#[inline]
fn wide_add(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a.saturating_add(b)
}
#[inline]
fn wide_sub(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a.saturating_sub(b)
}
#[inline]
fn from_wide_sat(w: Self::Wide) -> Self {
Q11i16(w.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
}
#[inline]
fn wide_is_positive(w: Self::Wide) -> bool {
w > 0
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct Q3i8(pub i8);
const Q3_FRAC: u32 = 3;
const Q3_ONE: i32 = 1 << Q3_FRAC;
impl LlrScalar for Q3i8 {
type Wide = i16;
const ZERO: Q3i8 = Q3i8(0);
const POS_INF_LIKE: Q3i8 = Q3i8(i8::MAX);
#[inline]
fn from_f32(x: f32) -> Self {
let v = (x * Q3_ONE as f32) as i32;
Q3i8(v.clamp(i8::MIN as i32, i8::MAX as i32) as i8)
}
#[inline]
fn to_f32(self) -> f32 {
(self.0 as f32) / (Q3_ONE as f32)
}
#[inline]
fn neg_sat(self) -> Self {
Q3i8(self.0.checked_neg().unwrap_or(i8::MAX))
}
#[inline]
fn abs_sat(self) -> Self {
Q3i8(self.0.saturating_abs())
}
#[inline]
fn is_negative(self) -> bool {
self.0 < 0
}
#[inline]
fn cmp_total(self, other: Self) -> Ordering {
self.0.cmp(&other.0)
}
#[inline]
fn mul_alpha(self, alpha: f32) -> Self {
let aq15 = (alpha * 32768.0) as i32;
let prod = (self.0 as i32) * aq15;
let v = prod >> 15;
Q3i8(v.clamp(i8::MIN as i32, i8::MAX as i32) as i8)
}
#[inline]
fn to_wide(self) -> Self::Wide {
self.0 as i16
}
#[inline]
fn wide_zero() -> Self::Wide {
0
}
#[inline]
fn wide_add(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a.saturating_add(b)
}
#[inline]
fn wide_sub(a: Self::Wide, b: Self::Wide) -> Self::Wide {
a.saturating_sub(b)
}
#[inline]
fn from_wide_sat(w: Self::Wide) -> Self {
Q3i8(w.clamp(i8::MIN as i16, i8::MAX as i16) as i8)
}
#[inline]
fn wide_is_positive(w: Self::Wide) -> bool {
w > 0
}
}
pub trait SpecScalar: Copy + Default + core::fmt::Debug {
type Wide: Copy + Default + core::fmt::Debug;
fn to_f32(self) -> f32;
fn from_f32(x: f32) -> Self;
fn from_f32_scaled(x: f32, scale: f32) -> Self;
const NEEDS_AUTOGAIN: bool;
fn norm_sqr_wide(re: Self, im: Self) -> Self::Wide;
fn wide_to_f32(w: Self::Wide) -> f32;
}
impl SpecScalar for f32 {
type Wide = f32;
const NEEDS_AUTOGAIN: bool = false;
#[inline]
fn to_f32(self) -> f32 {
self
}
#[inline]
fn from_f32(x: f32) -> Self {
x
}
#[inline]
fn from_f32_scaled(x: f32, _scale: f32) -> Self {
x
}
#[inline]
fn norm_sqr_wide(re: Self, im: Self) -> Self::Wide {
re * re + im * im
}
#[inline]
fn wide_to_f32(w: Self::Wide) -> f32 {
w
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct Q14i16(pub i16);
const Q14_FRAC: u32 = 14;
const Q14_ONE: i32 = 1 << Q14_FRAC;
impl SpecScalar for Q14i16 {
type Wide = i32;
const NEEDS_AUTOGAIN: bool = true;
#[inline]
fn to_f32(self) -> f32 {
(self.0 as f32) / (Q14_ONE as f32)
}
#[inline]
fn from_f32(x: f32) -> Self {
let v = (x * Q14_ONE as f32) as i32;
Q14i16(v.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
}
#[inline]
fn from_f32_scaled(x: f32, scale: f32) -> Self {
let v = (x * scale) as i32;
Q14i16(v.clamp(i16::MIN as i32, i16::MAX as i32) as i16)
}
#[inline]
fn norm_sqr_wide(re: Self, im: Self) -> Self::Wide {
let r = re.0 as i32;
let i = im.0 as i32;
r * r + i * i
}
#[inline]
fn wide_to_f32(w: Self::Wide) -> f32 {
(w as f32) / ((Q14_ONE as f32) * (Q14_ONE as f32))
}
}
pub type Cmplx<S> = num_complex::Complex<S>;
pub trait ComplexSpec<S: SpecScalar> {
fn norm_sqr_wide(self) -> S::Wide;
fn norm_sqr_f32(self) -> f32;
fn norm_f32(self) -> f32;
}
impl<S: SpecScalar> ComplexSpec<S> for Cmplx<S> {
#[inline]
fn norm_sqr_wide(self) -> S::Wide {
S::norm_sqr_wide(self.re, self.im)
}
#[inline]
fn norm_sqr_f32(self) -> f32 {
S::wide_to_f32(self.norm_sqr_wide())
}
#[inline]
fn norm_f32(self) -> f32 {
let n2 = self.norm_sqr_f32();
#[cfg(feature = "std")]
{
n2.sqrt()
}
#[cfg(not(feature = "std"))]
{
use num_traits::Float;
n2.sqrt()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn q11_round_trip() {
for f in [-10.0, -1.5, -0.001, 0.0, 0.001, 1.5, 10.0] {
let q = Q11i16::from_f32(f);
let back = q.to_f32();
assert!(
(f - back).abs() < 1.0 / Q11_ONE as f32 + 1e-6,
"f={f} back={back}"
);
}
}
#[test]
fn q11_saturation() {
assert_eq!(Q11i16::from_f32(1e6).0, i16::MAX);
assert_eq!(Q11i16::from_f32(-1e6).0, i16::MIN);
}
#[test]
fn q11_mul_alpha() {
let q = Q11i16::from_f32(8.0);
let scaled = q.mul_alpha(0.75);
let f = scaled.to_f32();
assert!((f - 6.0).abs() < 0.01, "f={f}");
}
#[test]
fn q11_neg_handles_min() {
let q = Q11i16(i16::MIN);
assert_eq!(q.neg_sat().0, i16::MAX);
}
#[test]
fn q3i8_round_trip() {
for f in [-15.0, -1.5, -0.125, 0.0, 0.125, 1.5, 15.0] {
let q = Q3i8::from_f32(f);
let back = q.to_f32();
assert!(
(f - back).abs() < 1.0 / Q3_ONE as f32 + 1e-6,
"f={f} back={back}"
);
}
}
#[test]
fn q3i8_saturation() {
assert_eq!(Q3i8::from_f32(1e6).0, i8::MAX);
assert_eq!(Q3i8::from_f32(-1e6).0, i8::MIN);
}
#[test]
fn q3i8_mul_alpha() {
let q = Q3i8::from_f32(8.0);
let scaled = q.mul_alpha(0.75);
let f = scaled.to_f32();
assert!((f - 6.0).abs() < 0.2, "f={f}");
}
#[test]
fn q3i8_neg_handles_min() {
let q = Q3i8(i8::MIN);
assert_eq!(q.neg_sat().0, i8::MAX);
}
#[test]
fn f32_mul_alpha_unchanged() {
assert!((8.0_f32.mul_alpha(0.75) - 6.0).abs() < 1e-6);
}
#[test]
fn q14_round_trip() {
for f in [-1.5, -0.001, 0.0, 0.001, 1.5] {
let q = Q14i16::from_f32(f);
let back = q.to_f32();
assert!(
(f - back).abs() < 1.0 / Q14_ONE as f32 + 1e-6,
"f={f} back={back}"
);
}
}
#[test]
fn cmplx_q14_norm_matches_f32() {
let cf = Cmplx::<f32>::new(0.6, 0.8); let cq = Cmplx::<Q14i16>::new(Q14i16::from_f32(0.6), Q14i16::from_f32(0.8));
assert!((cf.norm_sqr_f32() - 1.0).abs() < 1e-6);
assert!((cq.norm_sqr_f32() - 1.0).abs() < 1e-3);
}
#[test]
fn cmplx_layout_compat_with_num_complex() {
assert_eq!(
core::mem::size_of::<Cmplx<f32>>(),
core::mem::size_of::<num_complex::Complex<f32>>()
);
assert_eq!(
core::mem::align_of::<Cmplx<f32>>(),
core::mem::align_of::<num_complex::Complex<f32>>()
);
}
}