fixnum 0.9.2

Fixed-point numbers with explicit rounding
Documentation
use anyhow::Result;
#[cfg(feature = "i128")]
use proptest::prelude::*;

use fixnum::ops::Bounded;

mod macros;

#[test]
fn from_decimal() -> Result<()> {
    test_fixed_point! {
        case (input: (Layout, i32), expected: FixedPoint) => {
            assert_eq!(FixedPoint::from_decimal(input.0, input.1)?, expected);
            assert_eq!(FixedPoint::from_decimal(-input.0, input.1)?, expected.cneg().unwrap());
        },
        all {
            ((5_000_000_000, -9), fp!(5));
            ((1, 0), fp!(1));
            ((1, 1), fp!(10));
        },
        fp128 {
            ((5_000_000_000_000_000_000, -18), fp!(5));
        },
    };
    Ok(())
}

#[test]
fn to_decimal() -> Result<()> {
    test_fixed_point! {
        case (fp: FixedPoint, max_exponent: i32, expected: (Layout, i32)) => {
            let (mantissa, exponent) = fp.to_decimal(max_exponent);
            assert!(exponent <= max_exponent);
            assert_eq!((mantissa, exponent), expected);

            let (mantissa, exponent) = fp.cneg().unwrap().to_decimal(max_exponent);
            assert!(exponent <= max_exponent);
            assert_eq!((mantissa, exponent), (-expected.0, expected.1));
        },
        all {
            (fp!(0), 0, (0, 0));
            (fp!(0), -5, (0, -5));
            (fp!(0), 5, (0, 0));
            (fp!(50), 0, (50, 0));
            (fp!(50), 1, (5, 1));
            (fp!(50), 5, (5, 1));
            (fp!(50), i32::MAX, (5, 1));
            (fp!(50), -2, (5000, -2));
            (fp!(5.5), 0, (55, -1));
            (fp!(5.5), 1, (55, -1));
            (fp!(5.5), 5, (55, -1));
            (fp!(5.5), -1, (55, -1));
            (fp!(5.5), -2, (550, -2));
            (FixedPoint::MAX, 0, (FixedPoint::MAX.into_bits(), -FixedPoint::PRECISION))
        },
    };
    Ok(())
}

#[cfg(feature = "i128")]
proptest! {
    #[test]
    fn decimal_roundtrip(bits in any::<i128>(), zeros in 0u32..=38) {
        type FixedPoint128 = fixnum::FixedPoint<i128, typenum::U18>;

        let zeros = 10i128.pow(zeros);
        let expected = FixedPoint128::from_bits(bits / zeros * zeros);

        for max_exponent in -18..=5 {
            let (mantissa, exponent) = expected.to_decimal(max_exponent);
            let actual = FixedPoint128::from_decimal(mantissa, exponent).unwrap();
            prop_assert!(-FixedPoint128::PRECISION <= exponent && exponent <= max_exponent);
            prop_assert_eq!(actual, expected);

            if mantissa != 0 && exponent != max_exponent {
                prop_assert_ne!(mantissa % 10, 0);
            }
        }
    }
}