use crate::round_f32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Q9(i16);
impl Q9 {
const FRACTIONAL_BITS: u32 = 9;
const SCALE: f32 = (1 << Self::FRACTIONAL_BITS) as f32;
pub fn from_float(value: f32) -> Self {
let scaled = round_f32(value * Self::SCALE) as i16;
Q9(scaled)
}
pub fn to_float(self) -> f32 {
self.0 as f32 / Self::SCALE
}
pub fn from_raw(raw: i16) -> Self {
Q9(raw)
}
pub fn to_raw(self) -> i16 {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_q9_roundtrip() {
let values = [0.0, 0.5, -0.5, 0.998, -1.0];
for &v in &values {
let q = Q9::from_float(v);
let recovered = q.to_float();
assert!((recovered - v).abs() < 0.002, "Failed for {}", v);
}
}
#[test]
fn test_q9_boundary_values() {
let max_positive = Q9::from_float(0.998_046_9); assert_eq!(max_positive.to_float(), 0.998_046_9);
assert_eq!(max_positive.to_raw(), 511);
let max_negative = Q9::from_float(-1.0);
assert_eq!(max_negative.to_float(), -1.0);
assert_eq!(max_negative.to_raw(), -512);
let zero = Q9::from_float(0.0);
assert_eq!(zero.to_float(), 0.0);
assert_eq!(zero.to_raw(), 0);
}
#[test]
fn test_q9_precision_limits() {
let smallest_positive = Q9::from_raw(1);
assert_eq!(smallest_positive.to_float(), 1.0 / 512.0);
let largest_negative = Q9::from_raw(-1);
assert_eq!(largest_negative.to_float(), -1.0 / 512.0);
let val1 = Q9::from_raw(100);
let val2 = Q9::from_raw(101);
let diff = val2.to_float() - val1.to_float();
assert!((diff - 1.0 / 512.0).abs() < f32::EPSILON);
}
#[test]
fn test_q9_common_fractions() {
let half = Q9::from_float(0.5);
assert_eq!(half.to_float(), 0.5);
assert_eq!(half.to_raw(), 256);
let quarter = Q9::from_float(0.25);
assert_eq!(quarter.to_float(), 0.25);
assert_eq!(quarter.to_raw(), 128);
let eighth = Q9::from_float(0.125);
assert_eq!(eighth.to_float(), 0.125);
assert_eq!(eighth.to_raw(), 64);
let neg_half = Q9::from_float(-0.5);
assert_eq!(neg_half.to_float(), -0.5);
assert_eq!(neg_half.to_raw(), -256);
let neg_quarter = Q9::from_float(-0.25);
assert_eq!(neg_quarter.to_float(), -0.25);
assert_eq!(neg_quarter.to_raw(), -128);
}
#[test]
fn test_q9_rounding_behavior() {
let third = Q9::from_float(1.0 / 3.0);
let recovered_third = third.to_float();
assert!((recovered_third - (1.0 / 3.0)).abs() < 0.002);
let almost_half = Q9::from_float(0.5 + 0.0005); let rounded = almost_half.to_float();
assert!((rounded - 0.5).abs() < 0.002);
let tiny = Q9::from_float(0.0001);
assert!(tiny.to_float().abs() < 0.002); }
#[test]
fn test_q9_raw_conversions() {
let test_cases = [
(0, 0.0),
(256, 0.5),
(128, 0.25),
(64, 0.125),
(-256, -0.5),
(-512, -1.0),
(511, 0.998_046_9),
(-511, -0.998_046_9),
];
for &(raw, expected_float) in &test_cases {
let q9 = Q9::from_raw(raw);
let actual_float = q9.to_float();
assert!(
(actual_float - expected_float).abs() < 0.001,
"Raw {} -> expected {}, got {}",
raw,
expected_float,
actual_float
);
assert_eq!(q9.to_raw(), raw);
}
}
#[test]
fn test_q9_out_of_range_values() {
let too_large = Q9::from_float(2.0);
let recovered_large = too_large.to_float();
assert!(recovered_large.is_finite());
let too_small = Q9::from_float(-2.0);
let recovered_small = too_small.to_float();
assert!(recovered_small.is_finite());
let very_large = Q9::from_float(100.0);
let recovered_very_large = very_large.to_float();
assert!(recovered_very_large.is_finite());
let slightly_over = Q9::from_float(1.0);
let recovered = slightly_over.to_float();
assert!(recovered.is_finite());
}
#[test]
fn test_q9_equality() {
let a = Q9::from_float(0.5);
let b = Q9::from_float(0.5);
let c = Q9::from_raw(256); let d = Q9::from_float(0.25);
assert_eq!(a, b);
assert_eq!(a, c);
assert_ne!(a, d);
let close1 = Q9::from_raw(256);
let close2 = Q9::from_raw(257);
assert_ne!(close1, close2);
}
#[test]
fn test_q9_debug_trait() {
let half = Q9::from_float(0.5);
let _debug_ref = &half as &dyn core::fmt::Debug;
}
#[test]
fn test_q9_copy_clone() {
let original = Q9::from_float(0.75);
let copied = original;
assert_eq!(original, copied);
#[allow(clippy::clone_on_copy)]
let cloned = original.clone();
assert_eq!(original, cloned);
assert_eq!(copied.to_float(), 0.75);
assert_eq!(cloned.to_float(), 0.75);
}
#[test]
fn test_q9_automotive_use_cases() {
let throttle_idle = Q9::from_float(0.0);
let throttle_half = Q9::from_float(0.5);
let throttle_full = Q9::from_float(0.998);
assert_eq!(throttle_idle.to_float(), 0.0);
assert!((throttle_half.to_float() - 0.5).abs() < 0.001);
assert!(throttle_full.to_float() > 0.995);
let steering_left = Q9::from_float(-1.0);
let steering_center = Q9::from_float(0.0);
let steering_right = Q9::from_float(0.8);
assert_eq!(steering_left.to_float(), -1.0);
assert_eq!(steering_center.to_float(), 0.0);
assert!((steering_right.to_float() - 0.8).abs() < 0.002);
let brake_off = Q9::from_float(0.0);
let brake_light = Q9::from_float(0.1);
let brake_heavy = Q9::from_float(0.9);
assert_eq!(brake_off.to_float(), 0.0);
assert!((brake_light.to_float() - 0.1).abs() < 0.002);
assert!((brake_heavy.to_float() - 0.9).abs() < 0.002);
}
}