#[repr(transparent)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Q4_28(i32);
impl Q4_28 {
pub const FRAC_BITS: u32 = 28;
pub const ONE: Self = Self(1 << Self::FRAC_BITS);
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn from_raw(raw: i32) -> Self {
Self(raw)
}
#[must_use]
pub const fn to_raw(self) -> i32 {
self.0
}
#[must_use]
pub const fn row_from_raw(raw: [i32; 3]) -> [Self; 3] {
[Self(raw[0]), Self(raw[1]), Self(raw[2])]
}
#[must_use]
pub const fn mat3_from_raw(raw: [[i32; 3]; 3]) -> [[Self; 3]; 3] {
[
Self::row_from_raw(raw[0]),
Self::row_from_raw(raw[1]),
Self::row_from_raw(raw[2]),
]
}
#[must_use]
pub const fn saturating_add(self, rhs: Self) -> Self {
Self(self.0.saturating_add(rhs.0))
}
#[must_use]
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self(self.0.saturating_sub(rhs.0))
}
#[must_use]
pub const fn saturating_neg(self) -> Self {
Self(self.0.saturating_neg())
}
}
#[cfg(feature = "f32")]
impl Q4_28 {
#[must_use]
pub const fn to_f32(self) -> f32 {
(self.0 as f32) / (Self::ONE.0 as f32)
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Q0_16(u16);
impl Q0_16 {
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(u16::MAX);
#[must_use]
pub const fn from_raw(raw: u16) -> Self {
Self(raw)
}
#[must_use]
pub const fn to_raw(self) -> u16 {
self.0
}
#[must_use]
#[allow(clippy::indexing_slicing)] pub const fn array_from_raw<const N: usize>(raw: [u16; N]) -> [Self; N] {
let mut out = [Self::ZERO; N];
let mut i = 0;
while i < N {
out[i] = Self(raw[i]);
i = i.saturating_add(1);
}
out
}
#[must_use]
pub const fn saturating_add(self, rhs: Self) -> Self {
Self(self.0.saturating_add(rhs.0))
}
#[must_use]
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self(self.0.saturating_sub(rhs.0))
}
}
#[cfg(feature = "f32")]
impl Q0_16 {
#[must_use]
pub const fn to_f32(self) -> f32 {
(self.0 as f32) / (Self::ONE.0 as f32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_is_the_expected_bit_pattern() {
assert_eq!(Q4_28::ONE.to_raw(), 1 << 28);
assert_eq!(Q4_28::ZERO.to_raw(), 0);
}
#[test]
fn raw_round_trips() {
assert_eq!(Q4_28::from_raw(-123).to_raw(), -123);
assert_eq!(Q4_28::from_raw(i32::MAX).to_raw(), i32::MAX);
}
#[test]
fn saturating_ops_never_wrap() {
assert_eq!(
Q4_28::from_raw(i32::MAX)
.saturating_add(Q4_28::ONE)
.to_raw(),
i32::MAX
);
assert_eq!(
Q4_28::from_raw(i32::MIN)
.saturating_sub(Q4_28::ONE)
.to_raw(),
i32::MIN
);
}
#[test]
fn row_and_mat3_from_raw_wrap_elementwise() {
assert_eq!(
Q4_28::row_from_raw([1, 2, 3]),
[Q4_28::from_raw(1), Q4_28::from_raw(2), Q4_28::from_raw(3)]
);
assert_eq!(
Q4_28::mat3_from_raw([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
[
Q4_28::row_from_raw([1, 0, 0]),
Q4_28::row_from_raw([0, 1, 0]),
Q4_28::row_from_raw([0, 0, 1]),
]
);
}
#[cfg(feature = "f32")]
#[test]
fn to_f32_decodes_one_and_zero() {
assert_eq!(Q4_28::ONE.to_f32(), 1.0);
assert_eq!(Q4_28::ZERO.to_f32(), 0.0);
}
#[test]
fn q0_16_one_is_the_expected_bit_pattern() {
assert_eq!(Q0_16::ONE.to_raw(), u16::MAX);
assert_eq!(Q0_16::ZERO.to_raw(), 0);
}
#[test]
fn q0_16_raw_round_trips() {
assert_eq!(Q0_16::from_raw(123).to_raw(), 123);
assert_eq!(Q0_16::from_raw(u16::MAX).to_raw(), u16::MAX);
}
#[test]
fn q0_16_saturating_ops_never_wrap() {
assert_eq!(
Q0_16::from_raw(u16::MAX)
.saturating_add(Q0_16::ONE)
.to_raw(),
u16::MAX
);
assert_eq!(Q0_16::from_raw(0).saturating_sub(Q0_16::ONE).to_raw(), 0);
}
#[test]
fn q0_16_array_from_raw_wraps_elementwise() {
assert_eq!(
Q0_16::array_from_raw([1u16, 2, 3]),
[Q0_16::from_raw(1), Q0_16::from_raw(2), Q0_16::from_raw(3)]
);
assert_eq!(Q0_16::array_from_raw::<0>([]), []);
}
#[test]
fn q0_16_orders_like_its_raw_value() {
assert!(Q0_16::ZERO < Q0_16::ONE);
assert!(Q0_16::from_raw(100) < Q0_16::from_raw(200));
}
#[cfg(feature = "f32")]
#[test]
fn q0_16_to_f32_decodes_one_and_zero() {
assert_eq!(Q0_16::ONE.to_f32(), 1.0);
assert_eq!(Q0_16::ZERO.to_f32(), 0.0);
}
}