Search code examples
rustinteger-overflow

It is possible to define that a variable should always saturate on arithmetic operations?


I have a variable on which I do a lot of arithmetic operations.

My code was something like this:

let a: u32 = 3;
let res = (a*2) - 42

I had an overflow when a is too low, so I had to use saturating_sub() to solve the issue:

let a: u32 = 3;
let res = (a * 2).saturating_sub(42);

This does work very well, but saturating_sub is a bit less readable than -.

Since I have a lot of these operations, and that in every cases, I want a to saturate. I was wondering if it is possible to define that a should always saturate. And not replace every - with saturate_sub.


Solution

  • That's std::num::Saturating, however it only implements operations with other Saturating, not the underlying primitive:

    let a = Saturating(3u32);
    let res = (a * Saturating(2)) - Saturating(42);
    

    If that's a problem for you, you can create your own type:

    use std::num::Saturating as StdSaturating;
    use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
    
    #[derive(Clone, Copy, Debug)]
    pub struct Saturating<T>(pub T);
    
    macro_rules! impl_saturating_op {
        ( $trait:ident, $method:ident, $trait_assign:ident, $method_assign:ident => $($ty:ty),* ) => {
            $(
                impl $trait for Saturating<$ty> {
                    type Output = Saturating<$ty>;
                    fn $method(self, other: Saturating<$ty>) -> Self::Output {
                        Saturating(StdSaturating(self.0).$method(StdSaturating(other.0)).0)
                    }
                }
    
                impl $trait<$ty> for Saturating<$ty> {
                    type Output = Saturating<$ty>;
                    fn $method(self, other: $ty) -> Self::Output {
                        Saturating(StdSaturating(self.0).$method(StdSaturating(other)).0)
                    }
                }
    
                impl $trait<Saturating<$ty>> for $ty {
                    type Output = Saturating<$ty>;
                    fn $method(self, other: Saturating<$ty>) -> Self::Output {
                        Saturating(StdSaturating(self).$method(StdSaturating(other.0)).0)
                    }
                }
    
                impl $trait_assign for Saturating<$ty> {
                    fn $method_assign(&mut self, other: Saturating<$ty>) {
                        self.0 = StdSaturating(self.0).$method(StdSaturating(other.0)).0;
                    }
                }
    
                impl $trait_assign<$ty> for Saturating<$ty> {
                    fn $method_assign(&mut self, other: $ty) {
                        self.0 = StdSaturating(self.0).$method(StdSaturating(other)).0;
                    }
                }
            )*
        }
    }
    
    impl_saturating_op!(Add, add, AddAssign, add_assign => i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
    impl_saturating_op!(Sub, sub, SubAssign, sub_assign => i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
    impl_saturating_op!(Mul, mul, MulAssign, mul_assign => i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
    impl_saturating_op!(Div, div, DivAssign, div_assign => i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
    
    fn main() {
        let a = Saturating(3u32);
        let res = (a * 2) - 42;
    }