i_float 4.0.0

This fixed float math library provides an efficient and deterministic solution for arithmetic and geometric operations.
Documentation
use crate::int::number::int::IntNumber;
use crate::int::number::product_uint::UIntProduct;
use crate::int::number::uint::UIntNumber;
use crate::int::number::wide_int::WideIntNumber;
use core::marker::PhantomData;

pub struct FixedScale<I: IntNumber>(PhantomData<I>);

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

    #[inline(always)]
    pub fn half() -> I::Wide {
        Self::DENOMINATOR >> 1
    }

    #[inline(always)]
    pub fn to_int_round(scaled: I::Wide) -> I {
        I::from_wide(Self::div_round(scaled, Self::DENOMINATOR))
    }

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

        let negative = (numerator < I::Wide::ZERO) != (denominator < I::Wide::ZERO);
        let product = <I::WideUInt as UIntNumber>::Product::multiply(
            numerator.unsigned_abs(),
            Self::DENOMINATOR.to_uint(),
        );
        let quotient = product.divide_with_rounding(denominator.unsigned_abs());

        Self::from_unsigned_abs(quotient, negative)
    }

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

        let negative = (numerator < I::Wide::ZERO) != (denominator < I::Wide::ZERO);
        let product = <I::WideUInt as UIntNumber>::Product::from_uint(numerator.unsigned_abs());
        let quotient = product.divide_with_rounding(denominator.unsigned_abs());

        Self::from_unsigned_abs(quotient, negative)
    }

    #[inline(always)]
    fn from_unsigned_abs(value: I::WideUInt, negative: bool) -> I::Wide {
        debug_assert!(value <= I::WideUInt::LAST_BIT);

        if negative {
            if value == I::WideUInt::LAST_BIT {
                I::Wide::MIN
            } else {
                -I::Wide::from_uint(value)
            }
        } else {
            debug_assert!(value < I::WideUInt::LAST_BIT);
            I::Wide::from_uint(value)
        }
    }
}

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

    #[test]
    fn to_int_round_rounds_scaled_half_away_from_zero() {
        assert_eq!(FixedScale::<i32>::to_int_round(FixedScale::<i32>::half()), 1);
        assert_eq!(FixedScale::<i32>::to_int_round(-FixedScale::<i32>::half()), -1);
        assert_eq!(
            FixedScale::<i32>::to_int_round(FixedScale::<i32>::DENOMINATOR + FixedScale::<i32>::half()),
            2
        );
    }

    #[test]
    fn div_round_rounds_half_away_from_zero() {
        assert_eq!(FixedScale::<i32>::div_round(3, 2), 2);
        assert_eq!(FixedScale::<i32>::div_round(-3, 2), -2);
        assert_eq!(FixedScale::<i32>::div_round(3, -2), -2);
        assert_eq!(FixedScale::<i32>::div_round(-3, -2), 2);
    }

    #[test]
    fn div_round_preserves_min_negative_magnitude() {
        assert_eq!(FixedScale::<i16>::div_round(i32::MIN, 1), i32::MIN);
    }

    #[test]
    fn div_to_scaled_round_returns_scaled_ratio() {
        assert_eq!(
            FixedScale::<i32>::div_to_scaled_round(1, 2),
            FixedScale::<i32>::half()
        );
        assert_eq!(
            FixedScale::<i32>::div_to_scaled_round(-1, 2),
            -FixedScale::<i32>::half()
        );
    }
}