i_float 4.0.0

This fixed float math library provides an efficient and deterministic solution for arithmetic and geometric operations.
Documentation
use crate::float::number::FloatNumber;
use crate::int::number::fixed_scale::FixedScale;
use crate::int::number::int::IntNumber;
use crate::int::point::IntPoint;
use crate::int::vector::IntVector;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct UnitRatio<I: IntNumber> {
    value: I,
}

impl<I: IntNumber> UnitRatio<I> {
    pub const SCALE: u32 = FixedScale::<I>::SHIFT;
    pub const DENOMINATOR: I::Wide = FixedScale::<I>::DENOMINATOR;

    #[inline(always)]
    pub fn value(&self) -> I::Wide {
        self.value.to_wide()
    }

    #[inline(always)]
    pub fn new(value: I) -> Self {
        debug_assert!(value.to_wide() <= Self::DENOMINATOR);
        debug_assert!(value >= I::ZERO);
        Self { value }
    }

    #[inline(always)]
    pub fn from_float<F: FloatNumber>(value: F) -> Self {
        debug_assert!(value >= F::ZERO);
        debug_assert!(value <= F::ONE);

        let denominator = F::from_wide_int(Self::DENOMINATOR);
        Self::new(I::from_rounded_float(value * denominator))
    }

    #[inline(always)]
    pub fn from_int(numerator: I, denominator: I) -> Self {
        debug_assert!(numerator >= I::ZERO);
        debug_assert!(numerator <= denominator);
        debug_assert!(denominator > I::ZERO);

        let value = FixedScale::<I>::div_to_scaled_round(numerator.to_wide(), denominator.to_wide());
        Self::new(I::from_wide(value))
    }

    #[inline(always)]
    pub fn half() -> Self {
        Self::new(I::from_wide(FixedScale::<I>::half()))
    }

    #[inline(always)]
    pub fn mid(self, other: Self) -> Self {
        let value = (self.value() + other.value()) >> 1;
        Self::new(I::from_wide(value))
    }

    #[inline(always)]
    pub fn scale(self, scalar: I) -> I {
        FixedScale::<I>::to_int_round(self.scale_to_scaled_wide(scalar))
    }

    #[inline(always)]
    pub fn scale_to_scaled_wide(self, scalar: I) -> I::Wide {
        let s = self.value();
        s * scalar.to_wide()
    }

    #[inline(always)]
    pub fn scale_wide(self, scalar: I::Wide) -> I {
        let s = self.value();
        FixedScale::<I>::to_int_round(s * scalar)
    }

    #[inline(always)]
    pub fn scale_point(self, point: IntPoint<I>) -> IntPoint<I> {
        let x = self.scale(point.x);
        let y = self.scale(point.y);
        IntPoint { x, y }
    }

    #[inline(always)]
    pub fn scale_point_to_scaled_vector(self, point: IntPoint<I>) -> IntVector<I> {
        let x = self.scale_to_scaled_wide(point.x);
        let y = self.scale_to_scaled_wide(point.y);
        IntVector { x, y }
    }

    #[inline(always)]
    pub fn scale_vector_to_point(self, vector: IntVector<I>) -> IntPoint<I> {
        let x = self.scale_wide(vector.x);
        let y = self.scale_wide(vector.y);
        IntPoint { x, y }
    }
}

impl<I: IntNumber> IntPoint<I> {
    #[inline(always)]
    pub fn to_wide(self) -> IntVector<I> {
        IntVector {
            x: self.x.to_wide(),
            y: self.y.to_wide(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::UnitRatio;

    #[test]
    fn mid_uses_direct_average() {
        let a = UnitRatio::<i32>::new(2);
        let b = UnitRatio::<i32>::new(5);

        assert_eq!(a.mid(b).value, 3);
    }

    #[test]
    fn mid_preserves_upper_boundary() {
        let one_i16 = UnitRatio::<i16>::new(UnitRatio::<i16>::DENOMINATOR as i16);
        let one_i32 = UnitRatio::<i32>::new(UnitRatio::<i32>::DENOMINATOR as i32);
        let one_i64 = UnitRatio::<i64>::new(UnitRatio::<i64>::DENOMINATOR as i64);

        assert_eq!(one_i16.mid(one_i16), one_i16);
        assert_eq!(one_i32.mid(one_i32), one_i32);
        assert_eq!(one_i64.mid(one_i64), one_i64);
    }

    #[test]
    fn from_float_scales_unit_value() {
        let t = UnitRatio::<i32>::from_float(0.5_f32);

        assert_eq!(t.value(), UnitRatio::<i32>::DENOMINATOR >> 1);
    }

    #[test]
    fn from_int_scales_fraction() {
        let t = UnitRatio::<i32>::from_int(1, 3);
        let expected = ((UnitRatio::<i32>::DENOMINATOR + 1) / 3) as i32;

        assert_eq!(t.value, expected);
    }

    #[test]
    fn scale_rounds_half_away_from_zero() {
        let half = (UnitRatio::<i32>::DENOMINATOR >> 1) as i32;
        let t = UnitRatio::<i32>::new(half);

        assert_eq!(t.scale(1), 1);
        assert_eq!(t.scale(-1), -1);
        assert_eq!(t.scale(3), 2);
        assert_eq!(t.scale(-3), -2);
    }

    #[test]
    fn scale_wide_rounds_half_away_from_zero() {
        let half = (UnitRatio::<i32>::DENOMINATOR >> 1) as i32;
        let t = UnitRatio::<i32>::new(half);

        assert_eq!(t.scale_wide(1), 1);
        assert_eq!(t.scale_wide(-1), -1);
    }

    #[test]
    fn scale_to_scaled_wide_keeps_fixed_scale() {
        let t = UnitRatio::<i32>::half();

        assert_eq!(
            t.scale_to_scaled_wide(3),
            3 * (UnitRatio::<i32>::DENOMINATOR >> 1)
        );
    }
}