mathrust

How do I create a new number type in Rust?


I am want to create a number type, lets call it a sd32. The goal of this type to be stored like an i32, but when using it in arithmetic it should be treated as though it has 4 decimal places. The reason for this is so that any math performed on/with it should be as consistent and accurate as using integers, but it still has decimal places for when extra precision is required below whole numbers. (I fully understand that floats are still very precise and would probably work just fine, however I know exactly how much extra precision I want)

I am able to create something like this number type by wrapping a i32 in a struct and creating functions that allow me to input numbers as integers which are then multiplied by 10000 to move them up for the decimal places. I would also be able to implement each math expression to behave in the correct way to simulate having 4 decimal places. The issue is that it is not very seamless as I have to use them as variables and call each of the functions in awkward steps; I am unable to use them like other numbers in equations and just write it something like:

let variable = -30.52sd32 / 25u8 as sd32;

instead I would have to something akin to this which has extra steps and is more confusing in equations:

let mut variable = sd32::new(-30.52);
variable = variable.div(25);

Solution

  • You can use macros:

    use std::fmt;
    use std::ops::{Add, Div, Mul, Neg, Sub};
    
    #[derive(Copy, Clone, Debug, PartialEq)]
    pub struct Sd32(i32);
    
    impl Sd32 {
        const SCALE: i32 = 10_000;
    
        pub fn new(f: f32) -> Self {
            Sd32((f * Self::SCALE as f32) as i32)
        }
    
        pub fn from_raw(raw: i32) -> Self {
            Sd32(raw)
        }
    
        pub fn to_f32(self) -> f32 {
            self.0 as f32 / Self::SCALE as f32
        }
    }
    
    macro_rules! impl_from_int {
        ($($t:ty),*) => {
            $(
                impl From<$t> for Sd32 {
                    fn from(i: $t) -> Self {
                        Sd32(i as i32 * Sd32::SCALE)
                    }
                }
            )*
        };
    }
    
    impl_from_int!(i8, u8, i16, u16, i32);
    
    impl From<f32> for Sd32 {
        fn from(f: f32) -> Self {
            Sd32::new(f)
        }
    }
    
    macro_rules! impl_op {
        ($op:ident, $method:ident, $($t:ty),*) => {
            $(
                impl $op<$t> for Sd32 {
                    type Output = Sd32;
                    fn $method(self, rhs: $t) -> Sd32 {
                        self.$method(Sd32::from(rhs))
                    }
                }
            )*
        };
    }
    
    impl_op!(Add, add, i8, u8, i16, u16, i32, f32);
    impl_op!(Sub, sub, i8, u8, i16, u16, i32, f32);
    impl_op!(Mul, mul, i8, u8, i16, u16, i32, f32);
    impl_op!(Div, div, i8, u8, i16, u16, i32, f32);
    
    impl fmt::Display for Sd32 {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{:.4}", self.to_f32())
        }
    }
    
    impl Add for Sd32 {
        type Output = Sd32;
        fn add(self, rhs: Sd32) -> Sd32 {
            Sd32(self.0 + rhs.0)
        }
    }
    
    impl Sub for Sd32 {
        type Output = Sd32;
        fn sub(self, rhs: Sd32) -> Sd32 {
            Sd32(self.0 - rhs.0)
        }
    }
    
    impl Mul for Sd32 {
        type Output = Sd32;
        fn mul(self, rhs: Sd32) -> Sd32 {
            Sd32(((self.0 as i64 * rhs.0 as i64) / Sd32::SCALE as i64) as i32)
        }
    }
    
    impl Div for Sd32 {
        type Output = Sd32;
        fn div(self, rhs: Sd32) -> Sd32 {
            Sd32(((self.0 as i64 * Sd32::SCALE as i64) / rhs.0 as i64) as i32)
        }
    }
    
    impl Neg for Sd32 {
        type Output = Sd32;
        fn neg(self) -> Sd32 {
            Sd32(-self.0)
        }
    }
    
    fn main() {
        let variable = -Sd32::from(30.52) / 25u8;
        println!("Result: {}", variable);
    
        let a = Sd32::from(10.5);
        let res = a + 5i32 - 2.25f32;
        println!("Mixed ops: {}", res);
    
        println!("Sd32 + i32: {}", Sd32::from(10.0) + 5i32);
        println!("Sd32 + u8: {}", Sd32::from(10.0) + 5u8);
        println!("Sd32 + f32: {}", Sd32::from(10.0) + 5.5f32);
        println!("Sd32 / u8: {}", Sd32::from(100.0) / 4u8);
        println!("Sd32 * i32: {}", Sd32::from(3.5) * 2i32);
    
        let complex = Sd32::from(15.75) * 2i32 + 3.25f32 - 1u8;
        println!("Complex: {}", complex);
    }
    
    
    

    Prints

    Result: -1.2208
    Mixed ops: 13.2500
    Sd32 + i32: 15.0000
    Sd32 + u8: 15.0000
    Sd32 + f32: 15.5000
    Sd32 / u8: 25.0000
    Sd32 * i32: 7.0000
    Complex: 33.7500