int-interval 0.8.1

A small, no_std half-open interval algebra library for primitive integer types.
Documentation
use super::*;

#[cfg(test)]
mod unit_tests {
    use core::u8;

    use super::*;

    #[test]
    fn test_minkowski_add_basic() {
        let a = U8CO::try_new(1, 5).unwrap();
        let b = U8CO::try_new(2, 4).unwrap();
        let res = a.checked_minkowski_add(b).unwrap();
        // start = 1 + 2 = 3
        assert_eq!(res.start(), 3);
        // end_excl = (a.end_incl + b.end_incl + 1) = (4 + 3 + 1) = 8
        assert_eq!(res.end_excl(), 8);
    }

    #[test]
    fn test_minkowski_sub_basic() {
        let a = U8CO::try_new(5, 10).unwrap();
        let b = U8CO::try_new(2, 4).unwrap();
        let res = a.checked_minkowski_sub(b).unwrap();
        assert_eq!(res.start(), 2); // 5 - 3
        assert_eq!(res.end_excl(), 8); // 9 - 2 + 1
    }

    #[test]
    fn test_minkowski_mul_basic() {
        let a = U8CO::try_new(1, 4).unwrap(); // [1,4)
        let b = U8CO::try_new(2, 3).unwrap(); // [2,3)
        let res = a.checked_minkowski_mul_hull(b).unwrap();
        assert_eq!(res.start(), 2); // 1*2
        assert_eq!(res.end_excl(), 7); // (3*2)+1
    }

    #[test]
    fn test_minkowski_div_basic() {
        let a = U8CO::try_new(4, 10).unwrap();
        let b = U8CO::try_new(2, 5).unwrap();
        let res = a.checked_minkowski_div_hull(b).unwrap();
        assert_eq!(res.start(), 1); // 4/4
        assert_eq!(res.end_excl(), 5); // 9/2 + 1
    }

    #[test]
    fn test_minkowski_div_by_zero() {
        let a = U8CO::try_new(1, 5).unwrap();
        let b = U8CO::try_new(0, 3).unwrap();
        assert!(a.checked_minkowski_div_hull(b).is_none());
    }

    #[test]
    fn test_minkowski_overflow() {
        let a = U8CO::try_new(250, u8::MAX).unwrap();
        let b = U8CO::try_new(10, 20).unwrap();
        assert!(a.checked_minkowski_add(b).is_none());
        assert!(a.checked_minkowski_mul_hull(b).is_none());
    }
}

#[cfg(test)]
mod prop_tests {
    use super::*;
    use proptest::prelude::*;

    fn interval_strategy() -> impl Strategy<Value = U8CO> {
        prop_oneof![
            Just(U8CO::try_new(u8::MIN, u8::MIN + 1).unwrap()), // [0,1)
            Just(U8CO::try_new(u8::MIN, u8::MAX).unwrap()),     // [0,255)
            Just(U8CO::try_new(u8::MAX - 1, u8::MAX).unwrap()), // [254,255)
            (u8::MIN..=u8::MAX, u8::MIN..=u8::MAX)
                .prop_filter_map("valid interval", |(s, e)| U8CO::try_new(s, e))
        ]
    }

    #[inline]
    fn endpoints(x: U8CO) -> [u8; 2] {
        [x.start(), x.end_incl()]
    }

    proptest! {
        #[test]
        fn prop_checked_add_semantics(a in interval_strategy(), b in interval_strategy()) {
            let got = a.checked_minkowski_add(b);

            let expect_none =
                a.start().checked_add(b.start()).is_none()
                || a.end_excl().checked_add(b.end_incl()).is_none();

            prop_assert_eq!(got.is_none(), expect_none);

            if let Some(c) = got {
                prop_assert_eq!(c.start(), a.start() + b.start());
                prop_assert_eq!(c.end_incl(), a.end_incl() + b.end_incl());

                for &x in &endpoints(a) {
                    for &y in &endpoints(b) {
                        let z = x.checked_add(y).unwrap();
                        prop_assert!(c.contains(z));
                    }
                }
            }
        }

        #[test]
        fn prop_checked_sub_semantics(a in interval_strategy(), b in interval_strategy()) {
            let got = a.checked_minkowski_sub(b);

            let expect_none =
                a.start().checked_sub(b.end_incl()).is_none()
                || a.end_excl().checked_sub(b.start()).is_none();

            prop_assert_eq!(got.is_none(), expect_none);

            if let Some(c) = got {
                prop_assert_eq!(c.start(), a.start() - b.end_incl());
                prop_assert_eq!(c.end_incl(), a.end_incl() - b.start());

                for &x in &endpoints(a) {
                    for &y in &endpoints(b) {
                        let Some(z) = x.checked_sub(y) else {
                            continue;
                        };
                        prop_assert!(c.contains(z));
                    }
                }
            }
        }

        #[test]
        fn prop_checked_mul_semantics(a in interval_strategy(), b in interval_strategy()) {
            let got = a.checked_minkowski_mul_hull(b);

            let expect_none =
                a.start().checked_mul(b.start()).is_none()
                || a.end_incl().checked_mul(b.end_incl()).is_none()
                || a.end_incl()
                    .checked_mul(b.end_incl())
                    .and_then(|x| x.checked_add(1))
                    .is_none();

            prop_assert_eq!(got.is_none(), expect_none);

            if let Some(c) = got {
                prop_assert_eq!(c.start(), a.start() * b.start());
                prop_assert_eq!(c.end_incl(), a.end_incl() * b.end_incl());

                for &x in &endpoints(a) {
                    for &y in &endpoints(b) {
                        let Some(z) = x.checked_mul(y) else {
                            continue;
                        };
                        prop_assert!(c.contains(z));
                    }
                }
            }
        }

        #[test]
        fn prop_checked_div_semantics(
            a in interval_strategy(),
            b in interval_strategy().prop_filter("non-zero start", |b| b.start() != 0)
        ) {
            let got = a.checked_minkowski_div_hull(b);

            prop_assert!(got.is_some());

            let c = got.unwrap();
            prop_assert_eq!(c.start(), a.start() / b.end_incl());
            prop_assert_eq!(c.end_incl(), a.end_incl() / b.start());

            for &x in &endpoints(a) {
                for &y in &endpoints(b) {
                    let z = x / y;
                    prop_assert!(c.contains(z));
                }
            }
        }

        #[test]
        fn prop_add_commutative(a in interval_strategy(), b in interval_strategy()) {
            prop_assert_eq!(a.checked_minkowski_add(b), b.checked_minkowski_add(a));
        }

        #[test]
        fn prop_mul_commutative(a in interval_strategy(), b in interval_strategy()) {
            prop_assert_eq!(a.checked_minkowski_mul_hull(b), b.checked_minkowski_mul_hull(a));
        }
    }
}