light_ranged_integers 0.1.3

Ranged integers for stable Rust compiler, zero-dependencies and no unsafe code.
Documentation
/*
Copyright © 2024 - Massimo Gismondi

This file is part of light-ranged-integers.

light-ranged-integers is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

light-ranged-integers is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with light-ranged-integers.  If not, see <http://www.gnu.org/licenses/>.
*/

use crate::op_mode::{BringInRange, Wrap};

use super::op_mode;
use crate::*;

macro_rules! gen_wrap_bringinrange {
    ($i:ty, $bigger:ty) => {
        impl BringInRange<$i> for Wrap
        {
            fn bring_in_range(v: $i, min: $i, max: $i) -> $i
            {
                if v >= min && v <= max
                {
                    return v;
                }
                let v: $i = {
                    let min = min as $bigger;
                    let max = max as $bigger;
                    let v = v as $bigger;
                    let len = max - min + 1;

                    (v.rem_euclid(len) + (len - (min.rem_euclid(len))))
                        .rem_euclid(len)
                        .checked_add(min)
                        .unwrap()
                        .try_into()
                        .unwrap()
                };

                debug_assert!(v >= min && v <= max);
                return v;
            }
        }
    };
}

gen_wrap_bringinrange!(u8, u16);
gen_wrap_bringinrange!(u16, u32);
gen_wrap_bringinrange!(u32, u64);
gen_wrap_bringinrange!(u64, u128);
gen_wrap_bringinrange!(i8, i16);
gen_wrap_bringinrange!(i16, i32);
gen_wrap_bringinrange!(i32, i64);
gen_wrap_bringinrange!(i64, i128);

// Main add and sub are into the ranged.rs file

macro_rules! gen_wrap_impl {
    ($i:ty, $typename:ident) => {
        // Additions
        impl<const MIN: $i, const MAX: $i> std::ops::Add for $typename<MIN, MAX, op_mode::Wrap>
        {
            type Output = Self;
            fn add(self, rhs: $typename<MIN, MAX, op_mode::Wrap>) -> Self::Output
            {
                self + rhs.inner()
            }
        }
        impl<const MIN: $i, const MAX: $i> std::ops::AddAssign
            for $typename<MIN, MAX, op_mode::Wrap>
        {
            fn add_assign(&mut self, rhs: $typename<MIN, MAX, op_mode::Wrap>)
            {
                *self += rhs.inner();
            }
        }
        impl<const MIN: $i, const MAX: $i> std::ops::AddAssign<$i>
            for $typename<MIN, MAX, op_mode::Wrap>
        {
            fn add_assign(&mut self, rhs: $i)
            {
                *self = *self + rhs;
            }
        }

        // Subtraction
        impl<const MIN: $i, const MAX: $i> std::ops::Sub for $typename<MIN, MAX, op_mode::Wrap>
        {
            type Output = Self;
            fn sub(self, rhs: $typename<MIN, MAX, op_mode::Wrap>) -> Self::Output
            {
                self - rhs.inner()
            }
        }
        impl<const MIN: $i, const MAX: $i> std::ops::SubAssign
            for $typename<MIN, MAX, op_mode::Wrap>
        {
            fn sub_assign(&mut self, rhs: $typename<MIN, MAX, op_mode::Wrap>)
            {
                *self -= rhs.inner();
            }
        }
        impl<const MIN: $i, const MAX: $i> std::ops::SubAssign<$i>
            for $typename<MIN, MAX, op_mode::Wrap>
        {
            fn sub_assign(&mut self, rhs: $i)
            {
                *self = *self - rhs;
            }
        }
    };
}

gen_wrap_impl!(u8, RangedU8);
gen_wrap_impl!(u16, RangedU16);
gen_wrap_impl!(u32, RangedU32);
gen_wrap_impl!(u64, RangedU64);
gen_wrap_impl!(i8, RangedI8);
gen_wrap_impl!(i16, RangedI16);
gen_wrap_impl!(i32, RangedI32);
gen_wrap_impl!(i64, RangedI64);
#[cfg(test)]
mod tests
{
    use std::fmt::Display;

    use crate::{
        op_mode::{BringInRange, Wrap},
        RangedI8, RangedU8
    };

    fn run_test<T>(input: &[T], output: &[T], interval_min: T, interval_max: T)
    where
        T: Display + PartialEq + std::fmt::Debug + Copy,
        Wrap: BringInRange<T>
    {
        for (i, o) in input.iter().zip(output)
        {
            println!(
                "{} {}",
                Wrap::bring_in_range(*i, interval_min, interval_max),
                *o
            );
        }
        for (i, o) in input.iter().zip(output)
        {
            assert_eq!(Wrap::bring_in_range(*i, interval_min, interval_max), *o);
        }
    }

    #[test]
    fn test_against_stdlib()
    {
        assert_eq!(
            Wrap::bring_in_range(255 + 2, 0, 255),
            255u8.wrapping_add(2).into()
        );

        assert_eq!(
            Wrap::bring_in_range(255 + 255 + 2, 0, 255),
            255u8.wrapping_add(2).wrapping_add(255).into()
        );
        assert_eq!(
            Wrap::bring_in_range(255 + 2, 0, 255),
            255u8.wrapping_add(2).into()
        );

        assert_eq!(
            Wrap::bring_in_range(255 + 255 + 2, 0, 255),
            255u8.wrapping_add(2).wrapping_add(255).into()
        );
    }

    #[test]
    fn test_1()
    {
        assert_eq!(Wrap::bring_in_range(6, 0, 5), 0);
        assert_eq!(Wrap::bring_in_range(6, 1, 5), 1);
        assert_eq!(Wrap::bring_in_range(15, 0, 5), 15 % 6);
        // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
        // 0 1 2 3 4 5 0 1 2 3 4  5  0  1  2  3
        assert_eq!(Wrap::bring_in_range(15, 0, 5), 3);
    }

    #[test]
    fn test_2()
    {
        // Wrap around interval [2,4]
        // 0  1 [2  3  4] 5  6  7  8  9  10 11 12 13 14 15
        // 3  4  2  3  4  2  3  4  2  3  4  2  3  4  2  3
        const INPUTS: [u32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
        const OUTPUTS: [u32; 16] = [3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3];

        run_test(&INPUTS, &OUTPUTS, 2, 4);
    }

    #[test]
    fn test_3()
    {
        // Wrap around interval
        // 0  1  2  3  4  5 [6  7  8] 9  10 11 12 13 14 15
        // 6  7  8  6  7  8  6  7  8  6  7  8  6  7  8  6
        const INTERVAL: [u32; 2] = [6, 8];
        const INPUTS: [u32; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
        const OUTPUTS: [u32; 16] = [6, 7, 8, 6, 7, 8, 6, 7, 8, 6, 7, 8, 6, 7, 8, 6];

        run_test(&INPUTS, &OUTPUTS, INTERVAL[0], INTERVAL[1]);
    }

    #[test]
    fn test_4()
    {
        //
        // Wrap around interval
        // -5 -4 -3 -2 -1 0  1  2  3  4  5  6  7  8  9  10[11 12 13 14 15]16 17 18
        // 15 11 12 13 14 15 11 12 13 14 15 11 12 13 14 15 11 12 13 14 15 11 12 13
        const INTERVAL: [i32; 2] = [11, 15];
        const INPUTS: [i32; 24] = [
            -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
        ];
        const OUTPUTS: [i32; 24] = [
            15, 11, 12, 13, 14, 15, 11, 12, 13, 14, 15, 11, 12, 13, 14, 15, 11, 12, 13, 14, 15, 11,
            12, 13
        ];

        run_test(&INPUTS, &OUTPUTS, INTERVAL[0], INTERVAL[1]);
    }

    #[test]
    fn test_type_overflow()
    {
        let n1: RangedU8<10, 250, Wrap> = RangedU8::new(245);
        let n2: RangedU8<10, 250, Wrap> = RangedU8::new(13);
        assert_eq!(n1 + n2, 17);
        // -128,127
        let n1: RangedI8<-120, 100, Wrap> = RangedI8::new(-118);
        let n2: RangedI8<-120, 100, Wrap> = RangedI8::new(-13);
        let n3: RangedI8<-120, 100, Wrap> = RangedI8::new(13);
        assert_eq!(n1 + n2, 90);
        assert_eq!(n1 - n3, 90);

        let m = RangedU8::<5, 250, Wrap>::new(5);
        assert_eq!(m - 1, 250);
        assert_eq!(m - 10, 241);
    }

    #[test]
    fn test_unsigned_simple()
    {
        assert_eq!(RangedU8::<0, 255, Wrap>::new(250) + 6, 0);

        assert_eq!(RangedU8::<0, 254, Wrap>::new(250) + 6, 1);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(255) + 1, 10);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(254) + 2, 10);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(254) + 4, 12);
        assert_eq!(RangedU8::<10, 19, Wrap>::new(10) + 255, 15);
    }

    #[test]
    fn test_unsigned_subtraction()
    {
        assert_eq!(RangedU8::<0, 255, Wrap>::new(0) - 6, 250);

        assert_eq!(RangedU8::<0, 254, Wrap>::new(1) - 6, 250);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(10) - 1, 255);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(10) - 2, 254);

        assert_eq!(RangedU8::<10, 255, Wrap>::new(11) - 4, 253);

        assert_eq!(RangedU8::<10, 19, Wrap>::new(10) - 255, 15);
    }

    #[test]
    fn signed_add()
    {
        use std::ops::Add;
        assert_eq!(RangedI8::<-128, 127, Wrap>::new(127) + 1, -128);
        assert_eq!(RangedI8::<0, 127, Wrap>::new(127) + 1, 0);
        assert_eq!(RangedI8::<0, 127, Wrap>::new(127) + 1, 0);
        assert_eq!(RangedI8::<0, 127, Wrap>::new(0).add(-1), 127);
        assert_eq!(RangedI8::<-10, 37, Wrap>::new(-8).add(-5), 35);
        assert_eq!(RangedI8::<-35, 127, Wrap>::new(125) + 4, -34);
    }

    #[test]
    fn signed_sub()
    {
        use std::ops::Sub;
        assert_eq!(RangedI8::<-128, 127, Wrap>::new(-128).sub(1), 127);
        assert_eq!(RangedI8::<0, 127, Wrap>::new(0).sub(1), 127);
        assert_eq!(RangedI8::<-128, -10, Wrap>::new(-128).sub(1), -10);
        assert_eq!(RangedI8::<-128, -10, Wrap>::new(-128).sub(-3), -125);
        assert_eq!(RangedI8::<0, 127, Wrap>::new(125).sub(-5), 2);
        assert_eq!(RangedI8::<-10, 37, Wrap>::new(-8).sub(5), 35);
        assert_eq!(RangedI8::<-35, 127, Wrap>::new(125).sub(-4), -34);
    }
}