Skip to main content

nexus_decimal/
from_int.rs

1//! Per-(backing, D, IntType) `From` and `TryFrom` impls for `Decimal`.
2//!
3//! For each `(Backing, D)` pair, every primitive integer type is partitioned
4//! into one of two categories:
5//!
6//! - **Sound** — `IntType::MAX * 10^D` fits the backing (and `IntType::MIN`
7//!   side too). These get `impl From<IntType>`, which is infallible by
8//!   construction. The standard library's blanket
9//!   `impl<T, U: Into<T>> TryFrom<U> for T` then auto-provides `TryFrom`.
10//!
11//! - **Unsound** — overflow possible. For `i64` and `u64` only (preserving
12//!   the existing public API surface), these get `impl TryFrom<IntType>`
13//!   returning `ConvertError::Overflow` on overflow. Smaller integer types
14//!   (`i8`, `i16`, `i32`, `u8`, `u16`, `u32`) get no impl when unsound —
15//!   callers can widen explicitly.
16//!
17//! Self-backing (`i32 -> Decimal<i32, _>`, etc.) is only sound at `D = 0`
18//! (identity conversion, SCALE = 1). `From<backing>` is emitted at the
19//! specific (backing, 0) cells; at `D > 0` the conversion would overflow
20//! on large values and routes through `TryFrom` instead. `TryFrom<u64>`
21//! and cross-backing `TryFrom<i64>` continue to work via the unsound path.
22//!
23//! The truth table below was derived programmatically — see
24//! `tests/from_int.rs::truth_table_matches_verifier`.
25
26use crate::Decimal;
27use crate::error::ConvertError;
28
29/// Const-fn version of the runtime `is_sound` helper in
30/// `tests/from_int.rs`. Used inside [`impl_from_sound!`] to assert at
31/// compile time that every emitted `From<IntType> for Decimal<Backing, D>`
32/// is mathematically lossless. Returns `true` iff `IntType::MAX * 10^D`
33/// fits `Backing` and `IntType::MIN * 10^D` does too.
34///
35/// Semantics (rule + checked-arith propagation) mirror the runtime
36/// helper exactly. Any future drift is unlikely because both encode the
37/// same predicate; the compile-time path catches macro-table typos
38/// before they reach the runtime tests.
39pub(crate) const fn is_sound_const(
40    int_max: i128,
41    int_min: i128,
42    backing_max: i128,
43    backing_min: i128,
44    d: u32,
45) -> bool {
46    let Some(pow) = 10_i128.checked_pow(d) else {
47        return false;
48    };
49    let Some(max_scaled) = int_max.checked_mul(pow) else {
50        return false;
51    };
52    let Some(min_scaled) = int_min.checked_mul(pow) else {
53        return false;
54    };
55    max_scaled <= backing_max && min_scaled >= backing_min
56}
57
58macro_rules! impl_from_sound {
59    ($backing:ty, $d:literal, [$($int:ty),* $(,)?]) => {
60        $(
61            // Compile-time soundness check. Fails the build if this
62            // (backing, D, int) cell would overflow on
63            // IntType::MAX * 10^D or IntType::MIN * 10^D — catches
64            // accidentally including an unsound IntType in the list.
65            // Note: omissions (a sound IntType missing from the list)
66            // still compile silently; this asserts soundness of what
67            // is emitted, not coverage of what should be.
68            const _: () = assert!(
69                $crate::from_int::is_sound_const(
70                    <$int>::MAX as i128,
71                    <$int>::MIN as i128,
72                    <$backing>::MAX as i128,
73                    <$backing>::MIN as i128,
74                    $d,
75                ),
76                concat!(
77                    "nexus-decimal: impl_from_sound! invoked with unsound combination ",
78                    stringify!($int),
79                    " -> Decimal<",
80                    stringify!($backing),
81                    ", ",
82                    stringify!($d),
83                    ">"
84                ),
85            );
86
87            impl From<$int> for Decimal<$backing, $d> {
88                /// Lossless conversion. The compiler only emits this impl
89                /// when `IntType::MAX * 10^D` fits the backing — overflow
90                /// is impossible by construction (verified at compile time).
91                #[inline(always)]
92                fn from(value: $int) -> Self {
93                    Self {
94                        value: (value as $backing) * Self::SCALE,
95                    }
96                }
97            }
98        )*
99    };
100}
101
102macro_rules! impl_try_from_int {
103    ($backing:ty, $d:literal, signed: [$($int:ty),* $(,)?]) => {
104        $(
105            impl TryFrom<$int> for Decimal<$backing, $d> {
106                type Error = ConvertError;
107                #[inline]
108                fn try_from(value: $int) -> Result<Self, Self::Error> {
109                    Self::from_i64(value as i64).ok_or(ConvertError::Overflow)
110                }
111            }
112        )*
113    };
114    ($backing:ty, $d:literal, unsigned: [$($int:ty),* $(,)?]) => {
115        $(
116            impl TryFrom<$int> for Decimal<$backing, $d> {
117                type Error = ConvertError;
118                #[inline]
119                fn try_from(value: $int) -> Result<Self, Self::Error> {
120                    Self::from_u64(value as u64).ok_or(ConvertError::Overflow)
121                }
122            }
123        )*
124    };
125}
126
127// ===== Self-backing at D=0 =====
128//
129// At D=0, SCALE = 1, so conversion is a 1:1 identity mapping with no
130// possibility of overflow. These impls are the D=0-only exception to
131// the "self-backing goes through TryFrom" rule. Std's blanket
132// `impl<T, U: Into<T>> TryFrom<U> for T` auto-derives `TryFrom<backing>`
133// from these, so callers who want the fallible API surface still get it.
134
135impl From<i32> for Decimal<i32, 0> {
136    /// Identity conversion — at D=0, the backing value is the raw value.
137    #[inline(always)]
138    fn from(value: i32) -> Self {
139        Self { value }
140    }
141}
142
143impl From<i64> for Decimal<i64, 0> {
144    /// Identity conversion — at D=0, the backing value is the raw value.
145    #[inline(always)]
146    fn from(value: i64) -> Self {
147        Self { value }
148    }
149}
150
151impl From<i128> for Decimal<i128, 0> {
152    /// Identity conversion — at D=0, the backing value is the raw value.
153    #[inline(always)]
154    fn from(value: i128) -> Self {
155        Self { value }
156    }
157}
158
159// ===== i32 backing =====
160impl_from_sound!(i32, 0, [i8, i16, u8, u16]);
161impl_try_from_int!(i32, 0, signed: [i64]);
162impl_try_from_int!(i32, 0, unsigned: [u64]);
163impl_from_sound!(i32, 1, [i8, i16, u8, u16]);
164impl_try_from_int!(i32, 1, signed: [i64]);
165impl_try_from_int!(i32, 1, unsigned: [u64]);
166impl_from_sound!(i32, 2, [i8, i16, u8, u16]);
167impl_try_from_int!(i32, 2, signed: [i64]);
168impl_try_from_int!(i32, 2, unsigned: [u64]);
169impl_from_sound!(i32, 3, [i8, i16, u8, u16]);
170impl_try_from_int!(i32, 3, signed: [i64]);
171impl_try_from_int!(i32, 3, unsigned: [u64]);
172impl_from_sound!(i32, 4, [i8, i16, u8, u16]);
173impl_try_from_int!(i32, 4, signed: [i64]);
174impl_try_from_int!(i32, 4, unsigned: [u64]);
175impl_from_sound!(i32, 5, [i8, u8]);
176impl_try_from_int!(i32, 5, signed: [i64]);
177impl_try_from_int!(i32, 5, unsigned: [u64]);
178impl_from_sound!(i32, 6, [i8, u8]);
179impl_try_from_int!(i32, 6, signed: [i64]);
180impl_try_from_int!(i32, 6, unsigned: [u64]);
181impl_from_sound!(i32, 7, [i8]);
182impl_try_from_int!(i32, 7, signed: [i64]);
183impl_try_from_int!(i32, 7, unsigned: [u64]);
184impl_try_from_int!(i32, 8, signed: [i64]);
185impl_try_from_int!(i32, 8, unsigned: [u64]);
186impl_try_from_int!(i32, 9, signed: [i64]);
187impl_try_from_int!(i32, 9, unsigned: [u64]);
188
189// ===== i64 backing =====
190impl_from_sound!(i64, 0, [i8, i16, i32, u8, u16, u32]);
191// `TryFrom<i64>` auto-derived via std blanket from the `From<i64> for Decimal<i64, 0>`
192// impl above. Explicit `impl_try_from_int!(i64, 0, signed: [i64])` removed to
193// avoid coherence conflict.
194impl_try_from_int!(i64, 0, unsigned: [u64]);
195impl_from_sound!(i64, 1, [i8, i16, i32, u8, u16, u32]);
196impl_try_from_int!(i64, 1, signed: [i64]);
197impl_try_from_int!(i64, 1, unsigned: [u64]);
198impl_from_sound!(i64, 2, [i8, i16, i32, u8, u16, u32]);
199impl_try_from_int!(i64, 2, signed: [i64]);
200impl_try_from_int!(i64, 2, unsigned: [u64]);
201impl_from_sound!(i64, 3, [i8, i16, i32, u8, u16, u32]);
202impl_try_from_int!(i64, 3, signed: [i64]);
203impl_try_from_int!(i64, 3, unsigned: [u64]);
204impl_from_sound!(i64, 4, [i8, i16, i32, u8, u16, u32]);
205impl_try_from_int!(i64, 4, signed: [i64]);
206impl_try_from_int!(i64, 4, unsigned: [u64]);
207impl_from_sound!(i64, 5, [i8, i16, i32, u8, u16, u32]);
208impl_try_from_int!(i64, 5, signed: [i64]);
209impl_try_from_int!(i64, 5, unsigned: [u64]);
210impl_from_sound!(i64, 6, [i8, i16, i32, u8, u16, u32]);
211impl_try_from_int!(i64, 6, signed: [i64]);
212impl_try_from_int!(i64, 6, unsigned: [u64]);
213impl_from_sound!(i64, 7, [i8, i16, i32, u8, u16, u32]);
214impl_try_from_int!(i64, 7, signed: [i64]);
215impl_try_from_int!(i64, 7, unsigned: [u64]);
216impl_from_sound!(i64, 8, [i8, i16, i32, u8, u16, u32]);
217impl_try_from_int!(i64, 8, signed: [i64]);
218impl_try_from_int!(i64, 8, unsigned: [u64]);
219impl_from_sound!(i64, 9, [i8, i16, i32, u8, u16, u32]);
220impl_try_from_int!(i64, 9, signed: [i64]);
221impl_try_from_int!(i64, 9, unsigned: [u64]);
222impl_from_sound!(i64, 10, [i8, i16, u8, u16]);
223impl_try_from_int!(i64, 10, signed: [i64]);
224impl_try_from_int!(i64, 10, unsigned: [u64]);
225impl_from_sound!(i64, 11, [i8, i16, u8, u16]);
226impl_try_from_int!(i64, 11, signed: [i64]);
227impl_try_from_int!(i64, 11, unsigned: [u64]);
228impl_from_sound!(i64, 12, [i8, i16, u8, u16]);
229impl_try_from_int!(i64, 12, signed: [i64]);
230impl_try_from_int!(i64, 12, unsigned: [u64]);
231impl_from_sound!(i64, 13, [i8, i16, u8, u16]);
232impl_try_from_int!(i64, 13, signed: [i64]);
233impl_try_from_int!(i64, 13, unsigned: [u64]);
234impl_from_sound!(i64, 14, [i8, i16, u8, u16]);
235impl_try_from_int!(i64, 14, signed: [i64]);
236impl_try_from_int!(i64, 14, unsigned: [u64]);
237impl_from_sound!(i64, 15, [i8, u8]);
238impl_try_from_int!(i64, 15, signed: [i64]);
239impl_try_from_int!(i64, 15, unsigned: [u64]);
240impl_from_sound!(i64, 16, [i8, u8]);
241impl_try_from_int!(i64, 16, signed: [i64]);
242impl_try_from_int!(i64, 16, unsigned: [u64]);
243impl_try_from_int!(i64, 17, signed: [i64]);
244impl_try_from_int!(i64, 17, unsigned: [u64]);
245impl_try_from_int!(i64, 18, signed: [i64]);
246impl_try_from_int!(i64, 18, unsigned: [u64]);
247
248// ===== i128 backing =====
249impl_from_sound!(i128, 0, [i8, i16, i32, i64, u8, u16, u32, u64]);
250impl_from_sound!(i128, 1, [i8, i16, i32, i64, u8, u16, u32, u64]);
251impl_from_sound!(i128, 2, [i8, i16, i32, i64, u8, u16, u32, u64]);
252impl_from_sound!(i128, 3, [i8, i16, i32, i64, u8, u16, u32, u64]);
253impl_from_sound!(i128, 4, [i8, i16, i32, i64, u8, u16, u32, u64]);
254impl_from_sound!(i128, 5, [i8, i16, i32, i64, u8, u16, u32, u64]);
255impl_from_sound!(i128, 6, [i8, i16, i32, i64, u8, u16, u32, u64]);
256impl_from_sound!(i128, 7, [i8, i16, i32, i64, u8, u16, u32, u64]);
257impl_from_sound!(i128, 8, [i8, i16, i32, i64, u8, u16, u32, u64]);
258impl_from_sound!(i128, 9, [i8, i16, i32, i64, u8, u16, u32, u64]);
259impl_from_sound!(i128, 10, [i8, i16, i32, i64, u8, u16, u32, u64]);
260impl_from_sound!(i128, 11, [i8, i16, i32, i64, u8, u16, u32, u64]);
261impl_from_sound!(i128, 12, [i8, i16, i32, i64, u8, u16, u32, u64]);
262impl_from_sound!(i128, 13, [i8, i16, i32, i64, u8, u16, u32, u64]);
263impl_from_sound!(i128, 14, [i8, i16, i32, i64, u8, u16, u32, u64]);
264impl_from_sound!(i128, 15, [i8, i16, i32, i64, u8, u16, u32, u64]);
265impl_from_sound!(i128, 16, [i8, i16, i32, i64, u8, u16, u32, u64]);
266impl_from_sound!(i128, 17, [i8, i16, i32, i64, u8, u16, u32, u64]);
267impl_from_sound!(i128, 18, [i8, i16, i32, i64, u8, u16, u32, u64]);
268impl_from_sound!(i128, 19, [i8, i16, i32, i64, u8, u16, u32]);
269impl_try_from_int!(i128, 19, unsigned: [u64]);
270impl_from_sound!(i128, 20, [i8, i16, i32, u8, u16, u32]);
271impl_try_from_int!(i128, 20, signed: [i64]);
272impl_try_from_int!(i128, 20, unsigned: [u64]);
273impl_from_sound!(i128, 21, [i8, i16, i32, u8, u16, u32]);
274impl_try_from_int!(i128, 21, signed: [i64]);
275impl_try_from_int!(i128, 21, unsigned: [u64]);
276impl_from_sound!(i128, 22, [i8, i16, i32, u8, u16, u32]);
277impl_try_from_int!(i128, 22, signed: [i64]);
278impl_try_from_int!(i128, 22, unsigned: [u64]);
279impl_from_sound!(i128, 23, [i8, i16, i32, u8, u16, u32]);
280impl_try_from_int!(i128, 23, signed: [i64]);
281impl_try_from_int!(i128, 23, unsigned: [u64]);
282impl_from_sound!(i128, 24, [i8, i16, i32, u8, u16, u32]);
283impl_try_from_int!(i128, 24, signed: [i64]);
284impl_try_from_int!(i128, 24, unsigned: [u64]);
285impl_from_sound!(i128, 25, [i8, i16, i32, u8, u16, u32]);
286impl_try_from_int!(i128, 25, signed: [i64]);
287impl_try_from_int!(i128, 25, unsigned: [u64]);
288impl_from_sound!(i128, 26, [i8, i16, i32, u8, u16, u32]);
289impl_try_from_int!(i128, 26, signed: [i64]);
290impl_try_from_int!(i128, 26, unsigned: [u64]);
291impl_from_sound!(i128, 27, [i8, i16, i32, u8, u16, u32]);
292impl_try_from_int!(i128, 27, signed: [i64]);
293impl_try_from_int!(i128, 27, unsigned: [u64]);
294impl_from_sound!(i128, 28, [i8, i16, i32, u8, u16, u32]);
295impl_try_from_int!(i128, 28, signed: [i64]);
296impl_try_from_int!(i128, 28, unsigned: [u64]);
297impl_from_sound!(i128, 29, [i8, i16, u8, u16]);
298impl_try_from_int!(i128, 29, signed: [i64]);
299impl_try_from_int!(i128, 29, unsigned: [u64]);
300impl_from_sound!(i128, 30, [i8, i16, u8, u16]);
301impl_try_from_int!(i128, 30, signed: [i64]);
302impl_try_from_int!(i128, 30, unsigned: [u64]);
303impl_from_sound!(i128, 31, [i8, i16, u8, u16]);
304impl_try_from_int!(i128, 31, signed: [i64]);
305impl_try_from_int!(i128, 31, unsigned: [u64]);
306impl_from_sound!(i128, 32, [i8, i16, u8, u16]);
307impl_try_from_int!(i128, 32, signed: [i64]);
308impl_try_from_int!(i128, 32, unsigned: [u64]);
309impl_from_sound!(i128, 33, [i8, i16, u8, u16]);
310impl_try_from_int!(i128, 33, signed: [i64]);
311impl_try_from_int!(i128, 33, unsigned: [u64]);
312impl_from_sound!(i128, 34, [i8, u8]);
313impl_try_from_int!(i128, 34, signed: [i64]);
314impl_try_from_int!(i128, 34, unsigned: [u64]);
315impl_from_sound!(i128, 35, [i8, u8]);
316impl_try_from_int!(i128, 35, signed: [i64]);
317impl_try_from_int!(i128, 35, unsigned: [u64]);
318impl_from_sound!(i128, 36, [i8]);
319impl_try_from_int!(i128, 36, signed: [i64]);
320impl_try_from_int!(i128, 36, unsigned: [u64]);
321impl_try_from_int!(i128, 37, signed: [i64]);
322impl_try_from_int!(i128, 37, unsigned: [u64]);
323impl_try_from_int!(i128, 38, signed: [i64]);
324impl_try_from_int!(i128, 38, unsigned: [u64]);