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 @@
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;
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)
}
}
#[derive(Copy, Clone)]
pub struct Fixed32(i32);
impl Into<$from_type> for $impl_name {
fn into(self) -> $from_type {
impl From<i32> for Fixed32 {
fn from(value: i32) -> Self {
Fixed32(value.shl(FRACTION_BITS))
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
}
}
}
}
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)
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};
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord)]
pub struct $impl_name($raw_type);
pub const ZERO: $impl_name = $impl_name(0);
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 Into<i32> for Fixed32 {
fn into(self) -> i32 {
self.0.shr(FRACTION_BITS)
}
}
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_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 Add<Fixed32> for Fixed32 {
type Output = Fixed32;
fn add(self, rhs: Fixed32) -> Self::Output {
Fixed32(self.0 + rhs.0)
}
}
impl Sub<Fixed32> for Fixed32 {
type Output = Fixed32;
impl Add<$impl_name> for $impl_name {
type Output = $impl_name;
fn sub(self, rhs: Fixed32) -> Self::Output {
Fixed32(self.0 - rhs.0)
fn add(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0 + rhs.0)
}
}
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 Div<Fixed32> for Fixed32 {
type Output = Fixed32;
impl Sub<$impl_name> for $impl_name {
type Output = $impl_name;
fn div(self, rhs: Fixed32) -> Self::Output {
Fixed32((self.0 / rhs.0.shr(FRACTION_BITS / 2)).shl(FRACTION_BITS / 2))
fn sub(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0 - rhs.0)
}
}
impl Mul<$impl_name> for $impl_name {
type Output = $impl_name;
fn mul(self, rhs: $impl_name) -> Self::Output {
$impl_name(self.0.shr($frac_bits / 2) * rhs.0.shr($frac_bits / 2))
}
}
impl Div<$impl_name> for $impl_name {
type Output = $impl_name;
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 Default for $impl_name {
fn default() -> Self {
ZERO
}
}
impl $impl_name {
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;