implementation can be customized through macro

This commit is contained in:
Sven Vogel 2023-05-04 18:20:14 +02:00
parent 971906593d
commit 3295b67070
2 changed files with 129 additions and 57 deletions

View File

@ -1,3 +1,41 @@
# staticdot
Rust library for general purpose fixed point arithmetic
Rust library for general purpose fixed point arithmetic.
This library exposes the functionality to implement custom fixed point arithmetic
through the use of macros.
The most important macro is `impl_fixed_point`. This macro requires a name for the new types name, a raw type which will store the actual fixed point data
and the number of bits to use for the fraction.
The raw type used to store the fixed point data should be a primitive type of either:
* usize
* isize
* u8
* u16
* u32
* u64
* i8
* i16
* i32
* i64
Theoretically any type can be used if it implements the required traits.
Quick start example::
```rust
impl_fixed_point!(Fixed64_32, i64, 32);
fn main() {
let a = Fixed64_32::ZERO;
let b = Fixed64_32::from(3);
let c = a + b * Fixed64_32::from(4.5);
println!("{}", Into::<f32>::into(c));
}
```
## TODO
* add support for special functions such as sin(), sqrt(), ln()
* add macro for converting between different formats
* make precision retain shifts in operations customizable

View File

@ -1,84 +1,118 @@
macro_rules! __impl_from_into_for_rational {
($impl_name:ident, $raw_type:ty, $frac_bits:expr, $from_type:ty) => {
impl From<$from_type> for $impl_name {
fn from(value: $from_type) -> Self {
let integral = (value as $raw_type).shl($frac_bits);
let fraction = (value.fract() * 1.shl($frac_bits) as $from_type) as $raw_type;
$impl_name(integral | fraction)
}
}
impl Into<$from_type> for $impl_name {
fn into(self) -> $from_type {
let integral = self.0.shr($frac_bits);
let fraction = (self.0.bitand(FRAC_MASK)) as $from_type / (1 as $raw_type).shl($frac_bits) as $from_type;
integral as $from_type + fraction
}
}
}
}
macro_rules! impl_fixed_point {
($impl_name:ident, $raw_type:ty, $frac_bits:expr) => {
mod $impl_name {
use std::ops;
use std::ops::{Add, BitAnd, Div, Mul, Shl, Shr, Sub};
const INTEGER_BITS: i32 = 8;
const FRACTION_BITS: i32 = 24;
/// in oder to create the fraction bits mask we first fill all bits with 1 by initializing it with -1
/// then we reinterpret the -1 as unsigned and shift it to the left. We need to do this in order to
/// avoid arithmetic shifts
const FRACTION_MASK: i32 = (-1i32 as u32 >> INTEGER_BITS) as i32;
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord)]
pub struct $impl_name($raw_type);
#[derive(Copy, Clone)]
pub struct Fixed32(i32);
pub const ZERO: $impl_name = $impl_name(0);
impl From<i32> for Fixed32 {
fn from(value: i32) -> Self {
Fixed32(value.shl(FRACTION_BITS))
const FRAC_MASK: $raw_type = (-1) << (std::mem::size_of::<$raw_type>() * 8 - $frac_bits);
impl From<$raw_type> for $impl_name {
fn from(value: $raw_type) -> Self {
$impl_name(value.shl($frac_bits))
}
}
impl From<f32> for Fixed32 {
fn from(value: f32) -> Self {
let integral = (value as i32).shl(FRACTION_BITS);
let fraction = (value.fract() * 1.shl(FRACTION_BITS) as f32) as i32;
Fixed32(integral | fraction)
__impl_from_into_for_rational!($impl_name, $raw_type, $frac_bits, f32);
__impl_from_into_for_rational!($impl_name, $raw_type, $frac_bits, f64);
impl Into<$raw_type> for $impl_name {
fn into(self) -> $raw_type {
self.0.shr($frac_bits)
}
}
impl Into<i32> for Fixed32 {
fn into(self) -> i32 {
self.0.shr(FRACTION_BITS)
impl Add<$impl_name> for $impl_name {
type Output = $impl_name;
fn add(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0 + rhs.0)
}
}
impl Into<f32> for Fixed32 {
fn into(self) -> f32 {
let integral = self.0.shr(FRACTION_BITS) as f32;
let fraction = self.0.bitand(FRACTION_MASK) as f32 / 1.shl(FRACTION_BITS) as f32;
integral + fraction
impl Sub<$impl_name> for $impl_name {
type Output = $impl_name;
fn sub(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0 - rhs.0)
}
}
impl Add<Fixed32> for Fixed32 {
type Output = Fixed32;
impl Mul<$impl_name> for $impl_name {
type Output = $impl_name;
fn add(self, rhs: Fixed32) -> Self::Output {
Fixed32(self.0 + rhs.0)
fn mul(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0.shr($frac_bits / 2) * rhs.0.shr($frac_bits / 2))
}
}
impl Sub<Fixed32> for Fixed32 {
type Output = Fixed32;
impl Div<$impl_name> for $impl_name {
type Output = $impl_name;
fn sub(self, rhs: Fixed32) -> Self::Output {
Fixed32(self.0 - rhs.0)
fn div(self, rhs: $impl_name) -> Self::Output {
$impl_name((self.0 / rhs.0.shr($frac_bits / 2 as $raw_type)).shl($frac_bits / 2))
}
}
impl Mul<Fixed32> for Fixed32 {
type Output = Fixed32;
fn mul(self, rhs: Fixed32) -> Self::Output {
Fixed32(self.0.shr(FRACTION_BITS / 2) * rhs.0.shr(FRACTION_BITS / 2))
impl Default for $impl_name {
fn default() -> Self {
ZERO
}
}
impl Div<Fixed32> for Fixed32 {
type Output = Fixed32;
impl $impl_name {
fn div(self, rhs: Fixed32) -> Self::Output {
Fixed32((self.0 / rhs.0.shr(FRACTION_BITS / 2)).shl(FRACTION_BITS / 2))
pub fn integral(&self) -> $raw_type {
self.0.shr($frac_bits)
}
pub fn fraction_bits(&self) -> $raw_type {
self.0
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::tests::LowpFixed::LowpFixed;
use super::*;
impl_fixed_point!(LowpFixed, i32, 16);
impl_fixed_point!(HighpFixed, i64, 32);
#[test]
fn test() {
let a = Fixed32::from(9);
let b = Fixed32::from(3);
let a = HighpFixed::ZERO;
let b = HighpFixed::HighpFixed::from(3);
let c = a * a / b;