nexus-decimal 1.1.0

Fixed-point decimal arithmetic with compile-time precision
Documentation
//! Per-(backing, D, IntType) `From` and `TryFrom` impls for `Decimal`.
//!
//! For each `(Backing, D)` pair, every primitive integer type is partitioned
//! into one of two categories:
//!
//! - **Sound** — `IntType::MAX * 10^D` fits the backing (and `IntType::MIN`
//!   side too). These get `impl From<IntType>`, which is infallible by
//!   construction. The standard library's blanket
//!   `impl<T, U: Into<T>> TryFrom<U> for T` then auto-provides `TryFrom`.
//!
//! - **Unsound** — overflow possible. For `i64` and `u64` only (preserving
//!   the existing public API surface), these get `impl TryFrom<IntType>`
//!   returning `ConvertError::Overflow` on overflow. Smaller integer types
//!   (`i8`, `i16`, `i32`, `u8`, `u16`, `u32`) get no impl when unsound —
//!   callers can widen explicitly.
//!
//! Self-backing (`i32 -> Decimal<i32, _>`, etc.) is only sound at `D = 0`
//! (identity conversion, SCALE = 1). `From<backing>` is emitted at the
//! specific (backing, 0) cells; at `D > 0` the conversion would overflow
//! on large values and routes through `TryFrom` instead. `TryFrom<u64>`
//! and cross-backing `TryFrom<i64>` continue to work via the unsound path.
//!
//! The truth table below was derived programmatically — see
//! `tests/from_int.rs::truth_table_matches_verifier`.

use crate::Decimal;
use crate::error::ConvertError;

macro_rules! impl_from_sound {
    ($backing:ty, $d:literal, [$($int:ty),* $(,)?]) => {
        $(
            impl From<$int> for Decimal<$backing, $d> {
                /// Lossless conversion. The compiler only emits this impl
                /// when `IntType::MAX * 10^D` fits the backing — overflow
                /// is impossible by construction.
                #[inline(always)]
                fn from(value: $int) -> Self {
                    Self {
                        value: (value as $backing) * Self::SCALE,
                    }
                }
            }
        )*
    };
}

macro_rules! impl_try_from_int {
    ($backing:ty, $d:literal, signed: [$($int:ty),* $(,)?]) => {
        $(
            impl TryFrom<$int> for Decimal<$backing, $d> {
                type Error = ConvertError;
                #[inline]
                fn try_from(value: $int) -> Result<Self, Self::Error> {
                    Self::from_i64(value as i64).ok_or(ConvertError::Overflow)
                }
            }
        )*
    };
    ($backing:ty, $d:literal, unsigned: [$($int:ty),* $(,)?]) => {
        $(
            impl TryFrom<$int> for Decimal<$backing, $d> {
                type Error = ConvertError;
                #[inline]
                fn try_from(value: $int) -> Result<Self, Self::Error> {
                    Self::from_u64(value as u64).ok_or(ConvertError::Overflow)
                }
            }
        )*
    };
}

// ===== Self-backing at D=0 =====
//
// At D=0, SCALE = 1, so conversion is a 1:1 identity mapping with no
// possibility of overflow. These impls are the D=0-only exception to
// the "self-backing goes through TryFrom" rule. Std's blanket
// `impl<T, U: Into<T>> TryFrom<U> for T` auto-derives `TryFrom<backing>`
// from these, so callers who want the fallible API surface still get it.

impl From<i32> for Decimal<i32, 0> {
    /// Identity conversion — at D=0, the backing value is the raw value.
    #[inline(always)]
    fn from(value: i32) -> Self {
        Self { value }
    }
}

impl From<i64> for Decimal<i64, 0> {
    /// Identity conversion — at D=0, the backing value is the raw value.
    #[inline(always)]
    fn from(value: i64) -> Self {
        Self { value }
    }
}

impl From<i128> for Decimal<i128, 0> {
    /// Identity conversion — at D=0, the backing value is the raw value.
    #[inline(always)]
    fn from(value: i128) -> Self {
        Self { value }
    }
}

// ===== i32 backing =====
impl_from_sound!(i32, 0, [i8, i16, u8, u16]);
impl_try_from_int!(i32, 0, signed: [i64]);
impl_try_from_int!(i32, 0, unsigned: [u64]);
impl_from_sound!(i32, 1, [i8, i16, u8, u16]);
impl_try_from_int!(i32, 1, signed: [i64]);
impl_try_from_int!(i32, 1, unsigned: [u64]);
impl_from_sound!(i32, 2, [i8, i16, u8, u16]);
impl_try_from_int!(i32, 2, signed: [i64]);
impl_try_from_int!(i32, 2, unsigned: [u64]);
impl_from_sound!(i32, 3, [i8, i16, u8, u16]);
impl_try_from_int!(i32, 3, signed: [i64]);
impl_try_from_int!(i32, 3, unsigned: [u64]);
impl_from_sound!(i32, 4, [i8, i16, u8, u16]);
impl_try_from_int!(i32, 4, signed: [i64]);
impl_try_from_int!(i32, 4, unsigned: [u64]);
impl_from_sound!(i32, 5, [i8, u8]);
impl_try_from_int!(i32, 5, signed: [i64]);
impl_try_from_int!(i32, 5, unsigned: [u64]);
impl_from_sound!(i32, 6, [i8, u8]);
impl_try_from_int!(i32, 6, signed: [i64]);
impl_try_from_int!(i32, 6, unsigned: [u64]);
impl_from_sound!(i32, 7, [i8]);
impl_try_from_int!(i32, 7, signed: [i64]);
impl_try_from_int!(i32, 7, unsigned: [u64]);
impl_try_from_int!(i32, 8, signed: [i64]);
impl_try_from_int!(i32, 8, unsigned: [u64]);
impl_try_from_int!(i32, 9, signed: [i64]);
impl_try_from_int!(i32, 9, unsigned: [u64]);

// ===== i64 backing =====
impl_from_sound!(i64, 0, [i8, i16, i32, u8, u16, u32]);
// `TryFrom<i64>` auto-derived via std blanket from the `From<i64> for Decimal<i64, 0>`
// impl above. Explicit `impl_try_from_int!(i64, 0, signed: [i64])` removed to
// avoid coherence conflict.
impl_try_from_int!(i64, 0, unsigned: [u64]);
impl_from_sound!(i64, 1, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 1, signed: [i64]);
impl_try_from_int!(i64, 1, unsigned: [u64]);
impl_from_sound!(i64, 2, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 2, signed: [i64]);
impl_try_from_int!(i64, 2, unsigned: [u64]);
impl_from_sound!(i64, 3, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 3, signed: [i64]);
impl_try_from_int!(i64, 3, unsigned: [u64]);
impl_from_sound!(i64, 4, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 4, signed: [i64]);
impl_try_from_int!(i64, 4, unsigned: [u64]);
impl_from_sound!(i64, 5, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 5, signed: [i64]);
impl_try_from_int!(i64, 5, unsigned: [u64]);
impl_from_sound!(i64, 6, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 6, signed: [i64]);
impl_try_from_int!(i64, 6, unsigned: [u64]);
impl_from_sound!(i64, 7, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 7, signed: [i64]);
impl_try_from_int!(i64, 7, unsigned: [u64]);
impl_from_sound!(i64, 8, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 8, signed: [i64]);
impl_try_from_int!(i64, 8, unsigned: [u64]);
impl_from_sound!(i64, 9, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i64, 9, signed: [i64]);
impl_try_from_int!(i64, 9, unsigned: [u64]);
impl_from_sound!(i64, 10, [i8, i16, u8, u16]);
impl_try_from_int!(i64, 10, signed: [i64]);
impl_try_from_int!(i64, 10, unsigned: [u64]);
impl_from_sound!(i64, 11, [i8, i16, u8, u16]);
impl_try_from_int!(i64, 11, signed: [i64]);
impl_try_from_int!(i64, 11, unsigned: [u64]);
impl_from_sound!(i64, 12, [i8, i16, u8, u16]);
impl_try_from_int!(i64, 12, signed: [i64]);
impl_try_from_int!(i64, 12, unsigned: [u64]);
impl_from_sound!(i64, 13, [i8, i16, u8, u16]);
impl_try_from_int!(i64, 13, signed: [i64]);
impl_try_from_int!(i64, 13, unsigned: [u64]);
impl_from_sound!(i64, 14, [i8, i16, u8, u16]);
impl_try_from_int!(i64, 14, signed: [i64]);
impl_try_from_int!(i64, 14, unsigned: [u64]);
impl_from_sound!(i64, 15, [i8, u8]);
impl_try_from_int!(i64, 15, signed: [i64]);
impl_try_from_int!(i64, 15, unsigned: [u64]);
impl_from_sound!(i64, 16, [i8, u8]);
impl_try_from_int!(i64, 16, signed: [i64]);
impl_try_from_int!(i64, 16, unsigned: [u64]);
impl_try_from_int!(i64, 17, signed: [i64]);
impl_try_from_int!(i64, 17, unsigned: [u64]);
impl_try_from_int!(i64, 18, signed: [i64]);
impl_try_from_int!(i64, 18, unsigned: [u64]);

// ===== i128 backing =====
impl_from_sound!(i128, 0, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 1, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 2, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 3, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 4, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 5, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 6, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 7, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 8, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 9, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 10, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 11, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 12, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 13, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 14, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 15, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 16, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 17, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 18, [i8, i16, i32, i64, u8, u16, u32, u64]);
impl_from_sound!(i128, 19, [i8, i16, i32, i64, u8, u16, u32]);
impl_try_from_int!(i128, 19, unsigned: [u64]);
impl_from_sound!(i128, 20, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 20, signed: [i64]);
impl_try_from_int!(i128, 20, unsigned: [u64]);
impl_from_sound!(i128, 21, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 21, signed: [i64]);
impl_try_from_int!(i128, 21, unsigned: [u64]);
impl_from_sound!(i128, 22, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 22, signed: [i64]);
impl_try_from_int!(i128, 22, unsigned: [u64]);
impl_from_sound!(i128, 23, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 23, signed: [i64]);
impl_try_from_int!(i128, 23, unsigned: [u64]);
impl_from_sound!(i128, 24, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 24, signed: [i64]);
impl_try_from_int!(i128, 24, unsigned: [u64]);
impl_from_sound!(i128, 25, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 25, signed: [i64]);
impl_try_from_int!(i128, 25, unsigned: [u64]);
impl_from_sound!(i128, 26, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 26, signed: [i64]);
impl_try_from_int!(i128, 26, unsigned: [u64]);
impl_from_sound!(i128, 27, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 27, signed: [i64]);
impl_try_from_int!(i128, 27, unsigned: [u64]);
impl_from_sound!(i128, 28, [i8, i16, i32, u8, u16, u32]);
impl_try_from_int!(i128, 28, signed: [i64]);
impl_try_from_int!(i128, 28, unsigned: [u64]);
impl_from_sound!(i128, 29, [i8, i16, u8, u16]);
impl_try_from_int!(i128, 29, signed: [i64]);
impl_try_from_int!(i128, 29, unsigned: [u64]);
impl_from_sound!(i128, 30, [i8, i16, u8, u16]);
impl_try_from_int!(i128, 30, signed: [i64]);
impl_try_from_int!(i128, 30, unsigned: [u64]);
impl_from_sound!(i128, 31, [i8, i16, u8, u16]);
impl_try_from_int!(i128, 31, signed: [i64]);
impl_try_from_int!(i128, 31, unsigned: [u64]);
impl_from_sound!(i128, 32, [i8, i16, u8, u16]);
impl_try_from_int!(i128, 32, signed: [i64]);
impl_try_from_int!(i128, 32, unsigned: [u64]);
impl_from_sound!(i128, 33, [i8, i16, u8, u16]);
impl_try_from_int!(i128, 33, signed: [i64]);
impl_try_from_int!(i128, 33, unsigned: [u64]);
impl_from_sound!(i128, 34, [i8, u8]);
impl_try_from_int!(i128, 34, signed: [i64]);
impl_try_from_int!(i128, 34, unsigned: [u64]);
impl_from_sound!(i128, 35, [i8, u8]);
impl_try_from_int!(i128, 35, signed: [i64]);
impl_try_from_int!(i128, 35, unsigned: [u64]);
impl_from_sound!(i128, 36, [i8]);
impl_try_from_int!(i128, 36, signed: [i64]);
impl_try_from_int!(i128, 36, unsigned: [u64]);
impl_try_from_int!(i128, 37, signed: [i64]);
impl_try_from_int!(i128, 37, unsigned: [u64]);
impl_try_from_int!(i128, 38, signed: [i64]);
impl_try_from_int!(i128, 38, unsigned: [u64]);