205 lines
5.4 KiB
Rust
205 lines
5.4 KiB
Rust
//! Fixed point numbers.
|
|
|
|
use std::{fmt::{self, Write},
|
|
ops};
|
|
|
|
macro_rules! define_fixed_type {
|
|
(
|
|
$(#[$outer:meta])*
|
|
struct $Type:ident ($IT:ident) : $UT:ident, $LT:ident, $FracBits:expr;
|
|
) => {
|
|
$(#[$outer])*
|
|
#[derive(Clone, PartialEq, serde::Serialize)]
|
|
pub struct $Type($IT);
|
|
|
|
impl $Type
|
|
{
|
|
/// The number of fractional bits in this type.
|
|
pub const FRACBITS: $UT = $FracBits;
|
|
/// The integer mask for the fractional bits.
|
|
pub const FRACMASK: $UT = (1 << Self::FRACBITS) - 1;
|
|
/// The representation of `1.0` in this type.
|
|
pub const ONE: $IT = 1 << Self::FRACBITS;
|
|
|
|
/// Creates a value of this type with the bit pattern `bits`.
|
|
#[inline]
|
|
pub const fn from_bits(bits: $UT) -> Self {$Type(bits as $IT)}
|
|
|
|
/// Returns the raw bit pattern.
|
|
#[inline]
|
|
pub fn to_bits(&self) -> $UT {self.0 as $UT}
|
|
|
|
/// Sets the raw bit pattern to `bits`.
|
|
#[inline]
|
|
pub fn set_bits(&mut self, bits: $UT) {self.0 = bits as $IT}
|
|
|
|
/// Returns the integral portion.
|
|
#[inline]
|
|
pub fn integ(&self) -> $LT {(self.0 >> Self::FRACBITS) as $LT}
|
|
|
|
/// Returns the fractional portion.
|
|
///
|
|
/// This is a value of `1` to `FRACMASK`.
|
|
#[allow(trivial_numeric_casts)]
|
|
pub fn fract(&self) -> u16 {(self.0 as $UT & Self::FRACMASK) as u16}
|
|
|
|
/// Returns a multiplication by integer.
|
|
///
|
|
/// Panics if the result overflows.
|
|
#[inline]
|
|
pub fn mul_i(&self, n: $LT) -> Self {$Type(self.0 * $IT::from(n))}
|
|
|
|
/// Returns a division by integer.
|
|
///
|
|
/// Panics if `n` is `0`.
|
|
#[inline]
|
|
pub fn div_i(&self, n: $LT) -> Self {$Type(self.0 / $IT::from(n))}
|
|
|
|
#[inline]
|
|
fn fx_div(x: $IT, y: $IT) -> $IT
|
|
{
|
|
(i64::from(x) * i64::from(Self::ONE) / i64::from(y)) as $IT
|
|
}
|
|
|
|
#[inline]
|
|
fn fx_mul(x: $IT, y: $IT) -> $IT
|
|
{
|
|
(i64::from(x) * i64::from(y) / i64::from(Self::ONE)) as $IT
|
|
}
|
|
}
|
|
|
|
impl From<$LT> for $Type
|
|
{
|
|
#[inline]
|
|
fn from(n: $LT) -> Self {$Type($IT::from(n) << Self::FRACBITS)}
|
|
}
|
|
|
|
impl ops::Add for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn add(self, o: Self) -> Self {$Type(self.0 + o.0)}
|
|
}
|
|
|
|
impl ops::Sub for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn sub(self, o: Self) -> Self {$Type(self.0 - o.0)}
|
|
}
|
|
|
|
impl ops::Mul for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn mul(self, o: Self) -> Self {$Type(Self::fx_mul(self.0, o.0))}
|
|
}
|
|
|
|
impl ops::Div for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn div(self, o: Self) -> Self {$Type(Self::fx_div(self.0, o.0))}
|
|
}
|
|
|
|
impl ops::Neg for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn neg(self) -> Self {$Type(-self.0)}
|
|
}
|
|
|
|
impl ops::Not for $Type
|
|
{
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn not(self) -> Self {$Type(!self.0)}
|
|
}
|
|
|
|
impl fmt::Display for $Type
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
let prec = f.precision().unwrap_or(1);
|
|
let widt = f.width().unwrap_or(0);
|
|
write!(f, "{:widt$}.", self.integ(), widt = widt)?;
|
|
|
|
let mut k = self.to_bits();
|
|
for _ in 0..prec {
|
|
k &= Self::FRACMASK;
|
|
k *= 10;
|
|
let d = k >> Self::FRACBITS;
|
|
let d = d % 10;
|
|
f.write_char((d as u8 + b'0') as char)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for $Type
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
define_fixed_type! {
|
|
/// A fixed point type representing an angle.
|
|
///
|
|
/// The format of this type is `0.9s`, but because of the implementation,
|
|
/// the real format is `7.9s`.
|
|
struct Angle(i16) : u16, i8, 9;
|
|
}
|
|
|
|
define_fixed_type! {
|
|
/// A fixed point type representing a world unit.
|
|
///
|
|
/// The format of this type is `5.10s`. This has caused eternal suffering.
|
|
struct Unit(i16) : u16, i8, 10;
|
|
}
|
|
|
|
define_fixed_type! {
|
|
/// A generic fixed point type.
|
|
///
|
|
/// The format of this type is `15.16s`.
|
|
struct Fixed(i32) : u32, i16, 16;
|
|
}
|
|
|
|
#[test]
|
|
fn fixed_basic_ops()
|
|
{
|
|
let seven_div_2 = 3 << Fixed::FRACBITS | Fixed::FRACMASK / 2 + 1;
|
|
assert_eq!((Fixed::from(1) + 1.into()).to_bits(), 2 << Fixed::FRACBITS);
|
|
assert_eq!((Fixed::from(2) - 1.into()).to_bits(), 1 << Fixed::FRACBITS);
|
|
assert_eq!((Fixed::from(6) * 2.into()).to_bits(), 12 << Fixed::FRACBITS);
|
|
assert_eq!((Fixed::from(6).mul_i(2)).to_bits(), 12 << Fixed::FRACBITS);
|
|
assert_eq!((Fixed::from(7) / 2.into()).to_bits(), seven_div_2);
|
|
assert_eq!((Fixed::from(7).div_i(2)).to_bits(), seven_div_2);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
#[allow(unused_must_use)]
|
|
fn fixed_overflow() {Fixed::from(i16::max_value()) + 1.into();}
|
|
|
|
#[test]
|
|
fn fixed_printing()
|
|
{
|
|
assert_eq!(format!("{}", Fixed::from(6)), "6.0");
|
|
assert_eq!(format!("{}", Fixed::from(7).div_i(2)), "3.5");
|
|
assert_eq!(format!("{:7.7}", Fixed::from_bits(0xDEAD_BEEF)),
|
|
" -8531.7458343");
|
|
}
|
|
|
|
// EOF
|