unit-intervals 0.1.0

Constrained f32/f64 wrapper types for normalized [0, 1] and [-1, 1] values.
Documentation
#![cfg(feature = "num-traits")]

use num_traits::{
    AsPrimitive, Bounded, ConstOne, FromPrimitive, NumCast, One, Pow, ToBytes, ToPrimitive,
    ops::{
        checked::{CheckedMul, CheckedNeg},
        saturating::SaturatingMul,
    },
};
use unit_intervals::{SignedUnitInterval, UnitInterval};

#[test]
fn unit_interval_converts_to_primitives() {
    let value = UnitInterval::new(0.75_f32).unwrap();

    assert_eq!(value.to_f32(), Some(0.75));
    assert_eq!(value.to_f64(), Some(0.75));
    assert_eq!(value.to_i32(), Some(0));
    assert_eq!(value.to_u32(), Some(0));
}

#[test]
fn signed_unit_interval_converts_to_primitives() {
    let value = SignedUnitInterval::new(-0.75_f64).unwrap();

    assert_eq!(value.to_f32(), Some(-0.75));
    assert_eq!(value.to_f64(), Some(-0.75));
    assert_eq!(value.to_i32(), Some(0));
    assert_eq!(value.to_u32(), Some(0));
}

#[test]
fn primitive_conversions_validate_unit_interval() {
    assert_eq!(
        UnitInterval::<f32>::from_f32(0.25),
        Some(UnitInterval::new(0.25).unwrap())
    );
    assert_eq!(UnitInterval::<f32>::from_f32(1.25), None);
    assert_eq!(UnitInterval::<f32>::from_f32(f32::NAN), None);

    assert_eq!(
        <UnitInterval<f64> as NumCast>::from(1_u8),
        Some(UnitInterval::ONE)
    );
    assert_eq!(<UnitInterval<f64> as NumCast>::from(2_u8), None);
}

#[test]
fn primitive_conversions_validate_signed_unit_interval() {
    assert_eq!(
        SignedUnitInterval::<f32>::from_f32(-0.25),
        Some(SignedUnitInterval::new(-0.25).unwrap())
    );
    assert_eq!(SignedUnitInterval::<f32>::from_f32(-1.25), None);
    assert_eq!(SignedUnitInterval::<f32>::from_f32(f32::NAN), None);

    assert_eq!(
        <SignedUnitInterval<f64> as NumCast>::from(-1_i8),
        Some(SignedUnitInterval::NEG_ONE)
    );
    assert_eq!(<SignedUnitInterval<f64> as NumCast>::from(2_i8), None);
}

#[test]
fn bounds_match_constrained_intervals() {
    assert_eq!(UnitInterval::<f32>::min_value(), UnitInterval::ZERO);
    assert_eq!(UnitInterval::<f32>::max_value(), UnitInterval::ONE);

    assert_eq!(
        SignedUnitInterval::<f64>::min_value(),
        SignedUnitInterval::NEG_ONE
    );
    assert_eq!(
        SignedUnitInterval::<f64>::max_value(),
        SignedUnitInterval::ONE
    );
}

#[test]
fn to_bytes_delegates_to_backing_float() {
    let unit = UnitInterval::new(0.25_f32).unwrap();
    let signed = SignedUnitInterval::new(-0.25_f64).unwrap();

    assert_eq!(unit.to_be_bytes(), 0.25_f32.to_be_bytes());
    assert_eq!(unit.to_le_bytes(), 0.25_f32.to_le_bytes());
    assert_eq!(signed.to_be_bytes(), (-0.25_f64).to_be_bytes());
    assert_eq!(signed.to_le_bytes(), (-0.25_f64).to_le_bytes());
}

#[test]
fn as_primitive_converts_out_of_wrappers() {
    let unit = UnitInterval::new(0.25_f32).unwrap();
    let signed = SignedUnitInterval::new(-0.25_f64).unwrap();

    let unit_float: f64 = unit.as_();
    let unit_wide: UnitInterval<f64> = unit.as_();
    let signed_float: f32 = signed.as_();
    let signed_narrow: SignedUnitInterval<f32> = signed.as_();

    assert_eq!(unit_float, 0.25);
    assert_eq!(unit_wide, UnitInterval::new(0.25).unwrap());
    assert_eq!(signed_float, -0.25);
    assert_eq!(signed_narrow, SignedUnitInterval::new(-0.25).unwrap());
}

#[test]
fn one_traits_return_upper_bound() {
    assert_eq!(UnitInterval::<f32>::one(), UnitInterval::ONE);
    assert_eq!(
        <UnitInterval<f32> as ConstOne>::ONE,
        UnitInterval::<f32>::ONE
    );
    assert!(UnitInterval::<f32>::ONE.is_one());

    assert_eq!(SignedUnitInterval::<f64>::one(), SignedUnitInterval::ONE);
    assert_eq!(
        <SignedUnitInterval<f64> as ConstOne>::ONE,
        SignedUnitInterval::<f64>::ONE
    );
    assert!(SignedUnitInterval::<f64>::ONE.is_one());
}

#[test]
fn checked_mul_is_closed_over_intervals() {
    let unit = UnitInterval::new(0.5_f32).unwrap();
    let signed = SignedUnitInterval::new(-0.5_f64).unwrap();

    assert_eq!(CheckedMul::checked_mul(&unit, &unit), Some(unit * unit));
    assert_eq!(
        CheckedMul::checked_mul(&signed, &signed),
        Some(signed * signed)
    );
}

#[test]
fn saturating_mul_delegates_to_closed_multiplication() {
    let low = UnitInterval::new(0.25_f32).unwrap();
    let high = UnitInterval::new(0.75_f32).unwrap();

    assert_eq!(SaturatingMul::saturating_mul(&low, &high), low * high);

    let negative = SignedUnitInterval::new(-0.75_f64).unwrap();
    let positive = SignedUnitInterval::new(0.75_f64).unwrap();

    assert_eq!(
        SaturatingMul::saturating_mul(&negative, &positive),
        negative * positive
    );
}

#[test]
fn signed_unit_interval_supports_checked_negation() {
    let negative = SignedUnitInterval::new(-0.25_f32).unwrap();

    assert_eq!(
        CheckedNeg::checked_neg(&negative),
        Some(SignedUnitInterval::new(0.25).unwrap())
    );
}

#[test]
fn integer_powers_stay_in_interval() {
    let unit = UnitInterval::new(0.5_f32).unwrap();
    let signed = SignedUnitInterval::new(-0.5_f64).unwrap();

    assert_eq!(unit.pow(0_u8), UnitInterval::ONE);
    assert_eq!(unit.pow(3_u16), UnitInterval::new(0.125).unwrap());
    assert_eq!((&unit).pow(&2_u32), UnitInterval::new(0.25).unwrap());

    assert_eq!(signed.pow(0_usize), SignedUnitInterval::ONE);
    assert_eq!(signed.pow(3_u8), SignedUnitInterval::new(-0.125).unwrap());
    assert_eq!(
        (&signed).pow(&2_u16),
        SignedUnitInterval::new(0.25).unwrap()
    );
}