purrcision 0.2.1

decimals for nostd
Documentation
type Inner = u32;

const K_WIDTH: Inner = Inner::BITS;
const P_PRECISION: Inner = 9 * K_WIDTH / 32 - 2;
const E_EMAX: Inner = 3 * (2 as Inner).pow(K_WIDTH / 16 + 3);
const BIAS: Inner = E_EMAX + P_PRECISION - 2;

const SIGN_OFFSET: Inner = 0;
const SIGN_WIDTH: Inner = 1;
const SIGN_MASK: Inner = 1 << ((K_WIDTH - SIGN_WIDTH) - SIGN_OFFSET);
const SIGN_NEGATIVE: Inner = SIGN_MASK & Inner::MAX;
const SIGN_POSITIVE: Inner = SIGN_MASK & Inner::MIN;

const COMBINATION_FIELD_OFFSET: Inner = SIGN_WIDTH;
const COMBINATION_FIELD_WIDTH: Inner = K_WIDTH / 16 + 9;
const COMBINATION_TOP_BIT_SHIFT_AMOUNT: Inner = TRAILING_FIELD_WIDTH + COMBINATION_FIELD_WIDTH;
const COMBINATION_FIELD_MASK: Inner = ((1 << COMBINATION_FIELD_WIDTH) - 1)
    << ((K_WIDTH - COMBINATION_FIELD_WIDTH) - COMBINATION_FIELD_OFFSET);
const COMBINATION_INFINITY_MASK: Inner = (0b1_1111) << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 5);
const COMBINATION_INFINITY: Inner = 0b01_1110 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 5);

const COMBINATION_NAN_MASK: Inner = 0b011_1111 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 6);
const COMBINATION_NAN_QUIET: Inner = 0b011_1110 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 6);
const COMBINATION_NAN_SIGNALING: Inner = 0b011_1111 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 6);
const COMBINATION_BIG_FIRST_DIGIT_INDICATOR_MASK: Inner =
    0b1111 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 4);

const COMBINATION_BIG_FIRST_DIGIT_INDICATOR_V1: Inner =
    0b1110 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 4);
const COMBINATION_BIG_FIRST_DIGIT_INDICATOR_V2: Inner =
    0b1101 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 4);

const COMBINATION_BIG_FIRST_E_UP_MASK: Inner = 0b0011 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 4);

const COMBINATION_SMALL_FIRST_E_UP_MASK: Inner = 0b11 << (COMBINATION_TOP_BIT_SHIFT_AMOUNT - 2);

const COMBINATION_DOWN_MASK: Inner = ((1 << (COMBINATION_FIELD_WIDTH - 5)) - 1)
    << ((K_WIDTH - COMBINATION_FIELD_WIDTH) - COMBINATION_FIELD_OFFSET);

const TRAILING_FIELD_OFFSET: Inner = COMBINATION_FIELD_OFFSET + COMBINATION_FIELD_WIDTH;
const TRAILING_FIELD_WIDTH: Inner = 15 * K_WIDTH / 16 - 10;
const TRAILING_FIELD_MASK: Inner =
    ((1 << TRAILING_FIELD_WIDTH) - 1) << ((K_WIDTH - TRAILING_FIELD_WIDTH) - TRAILING_FIELD_OFFSET);

#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
struct D32BPB(Inner);

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DecimalCategory {
    /// `QuietNaN` (not a number): this value results from calculations like `(-1.0).sqrt()`.
    QuietNaN,
    /// `SignalingNaN` these usually come from graphics cards, or other external hardware
    SignalingNaN,

    /// Positive or negative infinity, which often results from dividing a nonzero number
    /// by zero.
    Infinite,

    /// A regular decimal point number, not any of the exceptional categories.
    Finite,
}

impl D32BPB {
    pub(crate) fn exponent(self) -> Option<i64> {
        self.biased_exponent()
            .map(|inner| i64::from(inner) - i64::from(BIAS))
    }

    pub(crate) const fn biased_exponent(self) -> Option<Inner> {
        match self.category() {
            DecimalCategory::Infinite
            | DecimalCategory::SignalingNaN
            | DecimalCategory::QuietNaN => None,
            DecimalCategory::Finite => Some(
                (if self.is_finite_with_big_first_digit() {
                    ((self.0) & COMBINATION_BIG_FIRST_E_UP_MASK) >> 1
                } else {
                    ((self.0) & COMBINATION_SMALL_FIRST_E_UP_MASK) >> 3
                } | ((self.0) & COMBINATION_DOWN_MASK))
                    >> TRAILING_FIELD_WIDTH,
            ),
        }
    }

    const fn is_finite_with_big_first_digit(self) -> bool {
        ((self.0 & COMBINATION_BIG_FIRST_DIGIT_INDICATOR_MASK)
            == COMBINATION_BIG_FIRST_DIGIT_INDICATOR_V1)
            || ((self.0 & COMBINATION_BIG_FIRST_DIGIT_INDICATOR_MASK)
                == COMBINATION_BIG_FIRST_DIGIT_INDICATOR_V2)
    }

    pub(crate) fn nan_signal(self) -> Option<Inner> {
        if self.category() == DecimalCategory::SignalingNaN {
            Some(self.0 & TRAILING_FIELD_MASK)
        } else {
            None
        }
    }

    pub(crate) const fn category(self) -> DecimalCategory {
        if (self.0 & COMBINATION_INFINITY_MASK) == COMBINATION_INFINITY {
            DecimalCategory::Infinite
        } else if (self.0 & COMBINATION_NAN_MASK) == COMBINATION_NAN_QUIET {
            DecimalCategory::QuietNaN
        } else if (self.0 & COMBINATION_NAN_MASK) == COMBINATION_NAN_SIGNALING {
            DecimalCategory::SignalingNaN
        } else {
            DecimalCategory::Finite
        }
    }

    pub(crate) const fn is_nan(self) -> bool {
        matches!(
            self.category(),
            DecimalCategory::QuietNaN | DecimalCategory::SignalingNaN
        )
    }

    pub(crate) const fn is_infinity(self) -> bool {
        matches!(self.category(), DecimalCategory::Infinite)
    }

    pub(crate) const fn is_positive(self) -> bool {
        match self.category() {
            DecimalCategory::Infinite | DecimalCategory::Finite => {
                self.0 & SIGN_MASK == SIGN_POSITIVE
            }
            _ => false,
        }
    }

    const fn is_negative(self) -> bool {
        match self.category() {
            DecimalCategory::Infinite | DecimalCategory::Finite => {
                self.0 & SIGN_MASK == SIGN_NEGATIVE
            }
            _ => false,
        }
    }
}

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

    #[test]
    fn it_represents_positive_infinity() {
        let result = D32BPB(0b1_1110 << (Inner::BITS - 6));
        assert!(result.is_positive());
        assert!(!result.is_negative());
        assert!(result.is_infinity());
        assert!(!result.is_nan());
        assert_eq!(result.category(), DecimalCategory::Infinite);
        assert_eq!(result.nan_signal(), None);
        assert!(result.biased_exponent().is_none());
    }

    #[test]
    fn it_represents_negative_infinity() {
        let result = D32BPB(0b11_1110 << (Inner::BITS - 6));
        assert!(!result.is_positive());
        assert!(result.is_negative());
        assert!(result.is_infinity());
        assert!(!result.is_nan());
        assert_eq!(result.category(), DecimalCategory::Infinite);
        assert_eq!(result.nan_signal(), None);
        assert!(result.biased_exponent().is_none());
    }

    #[test]
    fn it_represents_negative_finite_numbers() {
        let result = D32BPB(1 << (Inner::BITS - 1));
        assert!(result.is_negative());
        assert!(!result.is_positive());
        assert!(!result.is_nan());
        assert_eq!(result.category(), DecimalCategory::Finite);
        assert_eq!(result.nan_signal(), None);
        assert!(result.biased_exponent().is_some());
    }

    #[test]
    fn it_represents_positive_finite_numbers() {
        let result = D32BPB(0);
        assert!(!result.is_negative());
        assert!(result.is_positive());
        assert!(!result.is_nan());
        assert_eq!(result.category(), DecimalCategory::Finite);
        assert_eq!(result.nan_signal(), None);
        assert!(result.biased_exponent().is_some());
    }

    #[test]
    fn it_represents_quiet_nan() {
        let result = D32BPB(0b011_1110 << (Inner::BITS - 7));
        assert!(!result.is_negative());
        assert!(!result.is_positive());
        assert!(result.is_nan());
        assert_eq!(result.biased_exponent(), None);
        assert_eq!(result.category(), DecimalCategory::QuietNaN);
        assert_eq!(result.nan_signal(), None);
    }

    #[test]
    fn it_represents_signaling_nan() {
        let result = D32BPB((0b011_1111 << (Inner::BITS - 7)) | 0xABCDE);

        assert_eq!(result.category(), DecimalCategory::SignalingNaN);
        assert!(!result.is_negative());
        assert!(!result.is_positive());
        assert!(result.is_nan());
        assert_eq!(result.biased_exponent(), None);
        assert_eq!(result.nan_signal(), Some(0xABCDE));
    }

    #[test]
    fn it_can_get_the_biased_exponent_for_a_finite_number_with_big_first_digit_of_significand() {
        let bpb = D32BPB(0b0111_0011_1111 << (Inner::BITS - 12));
        let result = bpb.biased_exponent();
        assert_eq!(result, Some(0b1011_1111));
    }

    #[test]
    fn it_can_get_the_biased_exponent_for_a_finite_number_with_small_first_digit_of_significand() {
        let result = D32BPB(0b0100_0011_1111 << (Inner::BITS - 12));
        assert_eq!(result.biased_exponent(), Some(0b1011_1111));
    }

    #[test]
    fn it_can_get_the_exponent_for_a_finite_number_with_big_first_digit_of_significand() {
        let result = D32BPB(0b0111_0011_1111 << (Inner::BITS - 12));
        assert_eq!(result.exponent(), Some(0b1011_1111_i64 - 101));
    }

    #[test]
    fn it_can_get_the_exponent_for_a_finite_number_with_small_first_digit_of_significand() {
        let result = D32BPB(0b0100_0011_1111 << (Inner::BITS - 12));
        assert_eq!(result.exponent(), Some(0b1011_1111_i64 - 101));
    }

    #[test]
    fn it_has_a_default_of_0() {
        assert_eq!(D32BPB::default(), D32BPB(0));
    }
}