numeric-domains 0.1.0

Abstract domains for fixed-width numeric values
Documentation
use numeric_domains::Rnum;

const CONST_ONE: Rnum = Rnum::from_value(1);
const CONST_TWO: Rnum = Rnum::from_value(2);
const CONST_SUM: Rnum = CONST_ONE.add(CONST_TWO);
const CONST_DIFFERENCE: Rnum = CONST_ONE.subtract(CONST_TWO);
const CONST_NEGATION: Rnum = CONST_ONE.negate();
const CONST_NOT: Rnum = CONST_ONE.bit_not();
const CONST_PRODUCT: Rnum = CONST_TWO.multiply(CONST_TWO);
const CONST_DIVISION: Rnum = CONST_TWO.divide(CONST_ONE);
const CONST_REMAINDER: Rnum = CONST_TWO.remainder(CONST_ONE);

#[test]
fn operators_have_const_inherent_equivalents() {
    assert_eq!(CONST_SUM, CONST_ONE + CONST_TWO);
    assert_eq!(CONST_DIFFERENCE, CONST_ONE - CONST_TWO);
    assert_eq!(CONST_NEGATION, -CONST_ONE);
    assert_eq!(CONST_NOT, !CONST_ONE);
    assert_eq!(CONST_PRODUCT, CONST_TWO * CONST_TWO);
    assert_eq!(CONST_DIVISION, CONST_TWO / CONST_ONE);
    assert_eq!(CONST_REMAINDER, CONST_TWO % CONST_ONE);
}

#[test]
fn constructs_valid_ranges() {
    let range = Rnum::new(1, 10, -4, 7).expect("valid bounds");
    assert_eq!(range.unsigned_bounds(), (1, 10));
    assert_eq!(range.signed_bounds(), (-4, 7));
}

#[test]
fn rejects_reversed_bounds() {
    assert_eq!(Rnum::new(10, 1, -4, 7), None);
    assert_eq!(Rnum::new(1, 10, 7, -4), None);
}

#[test]
fn value_is_a_singleton_in_both_interpretations() {
    let range = Rnum::from_value(u64::MAX);
    assert_eq!(range.unsigned_bounds(), (u64::MAX, u64::MAX));
    assert_eq!(range.signed_bounds(), (-1, -1));
}

#[test]
fn common_domain_queries_handle_signed_and_unsigned_bounds() {
    let range = Rnum::new(0, u64::MAX, -1, 1).unwrap();
    assert!(range.contains_value(0));
    assert!(range.contains_value(1));
    assert!(range.contains_value(u64::MAX));
    assert_eq!(range.min_value(), Some(0));
    assert_eq!(range.max_value(), Some(u64::MAX));
    assert!(!range.is_const());

    let positive = Rnum::new(2, 4, 2, 4).unwrap();
    let negative = Rnum::new(u64::MAX, u64::MAX, -1, -1).unwrap();
    let union = positive.union(negative);
    assert!(union.contains(positive));
    assert!(union.contains(negative));
}

#[test]
fn default_contains_every_machine_value() {
    let range = Rnum::default();
    assert!(range.is_defined());
    assert!(range.contains_value(0));
    assert!(range.contains_value(u64::MAX));
}

#[test]
fn union_and_intersection_have_set_semantics() {
    let left = Rnum::new(1, 5, 1, 5).unwrap();
    let right = Rnum::new(4, 8, 4, 8).unwrap();
    let union = left.union(right);
    let intersection = left.intersection(right);

    for value in 0..10 {
        assert!(union.contains_value(value) || !left.contains_value(value));
        assert!(union.contains_value(value) || !right.contains_value(value));
        assert_eq!(
            intersection.contains_value(value),
            left.contains_value(value) && right.contains_value(value)
        );
    }

    let empty = Rnum::from_value(1).intersection(Rnum::from_value(2));
    assert!(!empty.has_value());
    assert!(!empty.contains_value(0));
    assert!(empty.union(left).contains(left));
}

#[test]
fn arithmetic_preserves_non_wrapping_bounds() {
    let left = Rnum::new(2, 4, 2, 4).unwrap();
    let right = Rnum::new(5, 7, 5, 7).unwrap();

    assert_eq!((left + right).unsigned_bounds(), (7, 11));
    assert_eq!((left + right).signed_bounds(), (7, 11));
    assert_eq!((right - left).unsigned_bounds(), (1, 5));
    assert_eq!((left * right).unsigned_bounds(), (10, 28));
    assert_eq!((right / left).unsigned_bounds(), (1, 3));
    assert_eq!((right % left).unsigned_bounds(), (0, 3));
    assert_eq!((!left).unsigned_bounds(), (!4, !2));
}

#[test]
fn wrapping_only_forgets_the_affected_interpretation() {
    let near_unsigned_max = Rnum::new(u64::MAX - 1, u64::MAX, -2, -1).unwrap();
    let one = Rnum::from_value(1);
    let result = near_unsigned_max + one;

    assert_eq!(result.unsigned_bounds(), (u64::MIN, u64::MAX));
    assert_eq!(result.signed_bounds(), (-1, 0));
    assert!(result.contains_value(u64::MAX));
    assert!(result.contains_value(0));
}

#[test]
fn division_ignores_zero_when_nonzero_divisors_are_available() {
    let dividend = Rnum::new(10, 20, 10, 20).unwrap();
    let divisor = Rnum::new(0, 4, 0, 4).unwrap();
    assert_eq!(
        dividend.checked_div(divisor).unwrap().unsigned_bounds(),
        (2, 20)
    );
    assert_eq!(dividend.checked_div(Rnum::from_value(0)), None);
}