use crate::error::{IffError, Result};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, Sub, Mul, Div, Neg};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Fixed(i32);
impl Fixed {
pub const FRAC_BITS: u32 = 16;
pub const SCALE: i32 = 1 << Self::FRAC_BITS;
pub const ZERO: Fixed = Fixed(0);
pub const ONE: Fixed = Fixed(Self::SCALE);
pub const NEG_ONE: Fixed = Fixed(-Self::SCALE);
pub const HALF: Fixed = Fixed(Self::SCALE / 2);
pub const PI: Fixed = Fixed(205887);
pub const TWO_PI: Fixed = Fixed(411775);
#[inline]
pub const fn from_raw(val: i32) -> Self {
Fixed(val)
}
#[inline]
pub const fn raw(self) -> i32 {
self.0
}
#[inline]
pub const fn from_int(val: i32) -> Self {
Fixed(val * Self::SCALE)
}
#[inline]
pub const fn to_int(self) -> i32 {
self.0 / Self::SCALE
}
#[inline]
pub const fn to_int_round(self) -> i32 {
(self.0 + Self::SCALE / 2) / Self::SCALE
}
#[inline]
pub fn from_f32(val: f32) -> Self {
Fixed((val * Self::SCALE as f32) as i32)
}
#[inline]
pub fn to_f32(self) -> f32 {
self.0 as f32 / Self::SCALE as f32
}
#[inline]
pub const fn abs(self) -> Self {
Fixed(self.0.abs())
}
#[inline]
pub const fn floor(self) -> Self {
Fixed(self.0 & !((1 << Self::FRAC_BITS) - 1))
}
#[inline]
pub const fn ceil(self) -> Self {
let mask = (1 << Self::FRAC_BITS) - 1;
if self.0 & mask == 0 {
Fixed(self.0)
} else {
Fixed((self.0 | mask) + 1)
}
}
#[inline]
pub const fn fract(self) -> Self {
Fixed(self.0 & ((1 << Self::FRAC_BITS) - 1))
}
#[inline]
pub fn mul_checked(self, rhs: Fixed) -> Result<Fixed> {
let product = (self.0 as i64) * (rhs.0 as i64);
let result = (product >> Self::FRAC_BITS) as i32;
if (product >> Self::FRAC_BITS) != result as i64 {
return Err(IffError::FixedPointOverflow {
operation: format!("{} * {}", self.to_f32(), rhs.to_f32()),
});
}
Ok(Fixed(result))
}
#[inline]
pub fn div_checked(self, rhs: Fixed) -> Result<Fixed> {
if rhs.0 == 0 {
return Err(IffError::FixedPointOverflow {
operation: "division by zero".to_string(),
});
}
let dividend = (self.0 as i64) << Self::FRAC_BITS;
let result = (dividend / rhs.0 as i64) as i32;
Ok(Fixed(result))
}
pub fn sqrt(self) -> Result<Fixed> {
if self.0 < 0 {
return Err(IffError::FixedPointOverflow {
operation: "sqrt of negative number".to_string(),
});
}
if self.0 == 0 {
return Ok(Fixed::ZERO);
}
let mut x = Fixed(self.0 >> 1);
for _ in 0..10 {
let x_div = self.div_checked(x)?;
let x_new = (x + x_div).0 >> 1;
if (x_new - x.0).abs() <= 1 {
break;
}
x = Fixed(x_new);
}
Ok(x)
}
pub fn sin_small(self) -> Fixed {
let x = self.0;
let x2 = ((x as i64 * x as i64) >> Self::FRAC_BITS) as i32;
let x3 = ((x2 as i64 * x as i64) >> Self::FRAC_BITS) as i32;
let x5 = ((x3 as i64 * x2 as i64) >> Self::FRAC_BITS) as i32;
let term1 = x;
let term2 = x3 / 6;
let term3 = x5 / 120;
Fixed(term1 - term2 + term3)
}
pub fn cos_small(self) -> Fixed {
let x = self.0;
let x2 = ((x as i64 * x as i64) >> Self::FRAC_BITS) as i32;
let x4 = ((x2 as i64 * x2 as i64) >> Self::FRAC_BITS) as i32;
let term1 = Self::SCALE;
let term2 = x2 / 2;
let term3 = x4 / 24;
Fixed(term1 - term2 + term3)
}
pub fn sin(self) -> Fixed {
let two_pi = Self::TWO_PI.0;
let mut x = self.0 % two_pi;
if x > Self::PI.0 {
x -= two_pi;
} else if x < -Self::PI.0 {
x += two_pi;
}
Fixed(x).sin_small()
}
pub fn cos(self) -> Fixed {
(self + Fixed(Self::PI.0 / 2)).sin()
}
#[inline]
pub const fn min(self, other: Fixed) -> Fixed {
if self.0 < other.0 {
self
} else {
other
}
}
#[inline]
pub const fn max(self, other: Fixed) -> Fixed {
if self.0 > other.0 {
self
} else {
other
}
}
#[inline]
pub const fn clamp(self, min: Fixed, max: Fixed) -> Fixed {
if self.0 < min.0 {
min
} else if self.0 > max.0 {
max
} else {
self
}
}
}
impl Add for Fixed {
type Output = Fixed;
#[inline]
fn add(self, rhs: Fixed) -> Fixed {
Fixed(self.0.wrapping_add(rhs.0))
}
}
impl Sub for Fixed {
type Output = Fixed;
#[inline]
fn sub(self, rhs: Fixed) -> Fixed {
Fixed(self.0.wrapping_sub(rhs.0))
}
}
impl Mul for Fixed {
type Output = Fixed;
#[inline]
fn mul(self, rhs: Fixed) -> Fixed {
let product = (self.0 as i64) * (rhs.0 as i64);
Fixed((product >> Self::FRAC_BITS) as i32)
}
}
impl Div for Fixed {
type Output = Fixed;
#[inline]
fn div(self, rhs: Fixed) -> Fixed {
let dividend = (self.0 as i64) << Self::FRAC_BITS;
Fixed((dividend / rhs.0 as i64) as i32)
}
}
impl Neg for Fixed {
type Output = Fixed;
#[inline]
fn neg(self) -> Fixed {
Fixed(self.0.wrapping_neg())
}
}
impl fmt::Display for Fixed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.6}", self.to_f32())
}
}
impl From<i32> for Fixed {
fn from(val: i32) -> Self {
Fixed::from_int(val)
}
}
impl From<u16> for Fixed {
fn from(val: u16) -> Self {
Fixed::from_int(val as i32)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_constants() {
assert_eq!(Fixed::ZERO.to_f32(), 0.0);
assert_eq!(Fixed::ONE.to_f32(), 1.0);
assert_eq!(Fixed::NEG_ONE.to_f32(), -1.0);
assert_eq!(Fixed::HALF.to_f32(), 0.5);
assert_relative_eq!(Fixed::PI.to_f32(), std::f32::consts::PI, epsilon = 0.001);
}
#[test]
fn test_from_int() {
assert_eq!(Fixed::from_int(5).to_int(), 5);
assert_eq!(Fixed::from_int(-3).to_int(), -3);
assert_eq!(Fixed::from_int(0).to_int(), 0);
}
#[test]
fn test_arithmetic() {
let a = Fixed::from_int(3);
let b = Fixed::from_int(2);
assert_eq!((a + b).to_int(), 5);
assert_eq!((a - b).to_int(), 1);
assert_eq!((a * b).to_int(), 6);
assert_eq!((a / b).to_int(), 1); }
#[test]
fn test_fractional() {
let a = Fixed::from_f32(3.5);
let b = Fixed::from_f32(2.25);
assert_relative_eq!((a + b).to_f32(), 5.75, epsilon = 0.01);
assert_relative_eq!((a - b).to_f32(), 1.25, epsilon = 0.01);
assert_relative_eq!((a * b).to_f32(), 7.875, epsilon = 0.01);
}
#[test]
fn test_sqrt() {
let tests = vec![
(4.0, 2.0),
(9.0, 3.0),
(2.0, 1.414),
(0.25, 0.5),
];
for (input, expected) in tests {
let result = Fixed::from_f32(input).sqrt().unwrap();
assert_relative_eq!(result.to_f32(), expected, epsilon = 0.01);
}
}
#[test]
fn test_trig() {
let angles = vec![0.0, 0.1, 0.3, 0.5];
for angle in angles {
let sin_result = Fixed::from_f32(angle).sin().to_f32();
let cos_result = Fixed::from_f32(angle).cos().to_f32();
assert_relative_eq!(sin_result, angle.sin(), epsilon = 0.15);
assert_relative_eq!(cos_result, angle.cos(), epsilon = 0.15);
}
}
#[test]
fn test_floor_ceil() {
let a = Fixed::from_f32(3.7);
assert_eq!(a.floor().to_int(), 3);
assert_eq!(a.ceil().to_int(), 4);
let b = Fixed::from_f32(-2.3);
assert_eq!(b.floor().to_int(), -3);
assert_eq!(b.ceil().to_int(), -2);
}
#[test]
fn test_min_max_clamp() {
let a = Fixed::from_int(5);
let b = Fixed::from_int(10);
assert_eq!(a.min(b), a);
assert_eq!(a.max(b), b);
let c = Fixed::from_int(7);
assert_eq!(c.clamp(a, b), c);
let d = Fixed::from_int(3);
assert_eq!(d.clamp(a, b), a);
let e = Fixed::from_int(15);
assert_eq!(e.clamp(a, b), b);
}
#[test]
fn test_determinism() {
let a = Fixed::from_int(7);
let b = Fixed::from_int(3);
for _ in 0..100 {
assert_eq!((a * b).raw(), (a * b).raw());
assert_eq!((a / b).raw(), (a / b).raw());
}
}
}