decimal-scaled 0.2.3

Const-generic base-10 fixed-point decimals (D9/D18/D38/D76/D153/D307) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
//! Macro-generated conversions between primitive types and the decimal
//! widths.
//!
//! Two surfaces live here:
//!
//! - **Infallible `From<$Src>`** — emitted by [`decl_from_primitive!`]
//! for source types that always fit the destination. Multiplies the
//! input by `multiplier()` (= `10^SCALE`); overflow follows Rust's
//! default integer arithmetic (debug-mode panic, release-mode wrap).
//! - **Fallible `TryFrom<$Src>`** — emitted by [`decl_try_from_i128!`],
//! [`decl_try_from_u128!`], [`decl_try_from_f64!`], and
//! [`decl_try_from_f32!`] for sources where the scaled magnitude may
//! exceed the destination's range, or where the input may be
//! non-finite (`f32` / `f64`). Returns
//! [`ConvertError::Overflow`] / [`ConvertError::NotFinite`] instead of
//! panicking.
//!
//! Plus [`decl_decimal_int_conversion_methods!`] which emits
//! `from_int` / `from_i32` / `to_int` / `to_int_with` on each width.
//!
//! [`ConvertError::Overflow`]: $crate::error::ConvertError::Overflow
//! [`ConvertError::NotFinite`]: $crate::error::ConvertError::NotFinite

/// Generates `From<$Src> for $Type<SCALE>` that scales the value by
/// `10^SCALE` and stores it in `$Storage`. The cast `value as $Storage`
/// happens first when the source is narrower than the storage, which
/// is the lossless case; the subsequent multiply is the overflow risk.
macro_rules! decl_from_primitive {
    // Wide storage: the primitive widens into wide
    // storage via the `WideInt` cast, then scales by `10^SCALE`.
    (wide $Type:ident, $Storage:ty, $Src:ty) => {
        impl<const SCALE: u32> ::core::convert::From<$Src> for $Type<SCALE> {
            /// Constructs from an integer by scaling to `value * 10^SCALE`.
            /// Overflows follow the wide integer's default arithmetic semantics
            /// (debug-mode panic, release-mode wrap).
            #[inline]
            fn from(value: $Src) -> Self {
                let widened: $Storage = $crate::wide_int::wide_cast(value as i128);
                Self(widened * Self::multiplier())
            }
        }
    };

    // Native (primitive integer) storage.
    ($Type:ident, $Storage:ty, $Src:ty) => {
        impl<const SCALE: u32> ::core::convert::From<$Src> for $Type<SCALE> {
            /// Constructs from an integer by scaling to `value * 10^SCALE`.
            /// Overflows follow Rust's default integer arithmetic semantics
            /// (debug-mode panic, release-mode wrap).
            #[inline]
            fn from(value: $Src) -> Self {
                Self((value as $Storage) * Self::multiplier())
            }
        }
    };
}

pub(crate) use decl_from_primitive;

/// Generates `From<$Src<SCALE>> for $Dest<SCALE>` for a lossless
/// widening conversion (e.g. D9 -> D18, D18 -> D38). `$SrcStorage`
/// must widen losslessly to `$DestStorage` via an `as` cast.
macro_rules! decl_cross_width_widening {
    // Widening *into* wide storage. The source storage is
    // a primitive integer or a narrower wide integer; either way
    // the `WideInt` cast performs the lossless widen.
    (wide $Dest:ident, $DestStorage:ty, $Src:ident, $SrcStorage:ty) => {
        impl<const SCALE: u32> ::core::convert::From<$Src<SCALE>> for $Dest<SCALE> {
            /// Widens a narrower decimal type to this wider one. The
            /// scale is unchanged; the storage is widened via
            /// the `WideInt` cast (lossless because the source domain is
            /// a subset of the destination).
            #[inline]
            fn from(value: $Src<SCALE>) -> Self {
                Self($crate::wide_int::wide_cast(value.to_bits()))
            }
        }
    };

    // Native-to-native widening.
    ($Dest:ident, $DestStorage:ty, $Src:ident, $SrcStorage:ty) => {
        impl<const SCALE: u32> ::core::convert::From<$Src<SCALE>> for $Dest<SCALE> {
            /// Widens a narrower decimal type to this wider one. The
            /// scale is unchanged and the storage is widened by an
            /// `as` cast (lossless because the source storage type is
            /// strictly narrower than the destination).
            #[inline]
            fn from(value: $Src<SCALE>) -> Self {
                Self(value.to_bits() as $DestStorage)
            }
        }
    };
}

pub(crate) use decl_cross_width_widening;

/// Generates `TryFrom<$Src<SCALE>> for $Dest<SCALE>` for a fallible
/// narrowing conversion. Returns
/// `Err(ConvertError::OutOfRange)` when the source value exceeds
/// the destination's representable range; otherwise returns the
/// narrowed value bit-for-bit (same logical decimal value).
macro_rules! decl_cross_width_narrowing {
    // Narrowing *from* wide storage. The destination may
    // be a primitive integer (e.g. D76 -> D38) or a narrower wide integer
    // integer (e.g. D153 -> D76); the `WideInt` cast handles the bound
    // widening and the final narrowing cast in both cases.
    (wide $Dest:ident, $DestStorage:ty, $Src:ident, $SrcStorage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<$Src<SCALE>> for $Dest<SCALE> {
            type Error = $crate::error::ConvertError;
            /// Attempts to narrow a wider decimal type to this narrower
            /// one. Fails with `Overflow` when the source value exceeds
            /// the destination's `MIN..=MAX`. The scale is unchanged.
            #[inline]
            fn try_from(value: $Src<SCALE>) -> ::core::result::Result<Self, Self::Error> {
                let bits = value.to_bits();
                let dest_max: $SrcStorage = $crate::wide_int::wide_cast(<$DestStorage>::MAX);
                let dest_min: $SrcStorage = $crate::wide_int::wide_cast(<$DestStorage>::MIN);
                if bits > dest_max || bits < dest_min {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::Overflow,
                    );
                }
                ::core::result::Result::Ok(Self($crate::wide_int::wide_cast(bits)))
            }
        }
    };

    // Native-to-native narrowing.
    ($Dest:ident, $DestStorage:ty, $Src:ident, $SrcStorage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<$Src<SCALE>> for $Dest<SCALE> {
            type Error = $crate::error::ConvertError;
            /// Attempts to narrow a wider decimal type to this narrower
            /// one. Fails with `OutOfRange` when the source value
            /// exceeds the destination's `MIN..=MAX`. The scale is
            /// unchanged.
            #[inline]
            fn try_from(value: $Src<SCALE>) -> ::core::result::Result<Self, Self::Error> {
                let bits = value.to_bits();
                if bits > (<$DestStorage>::MAX as $SrcStorage)
                    || bits < (<$DestStorage>::MIN as $SrcStorage)
                {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::Overflow,
                    );
                }
                ::core::result::Result::Ok(Self(bits as $DestStorage))
            }
        }
    };
}

pub(crate) use decl_cross_width_narrowing;

/// Emits `TryFrom<i128> for $Type<SCALE>` returning
/// `Result<Self, ConvertError::Overflow>` after `checked_mul` by the
/// multiplier and a narrowing range-check against `$Storage`.
macro_rules! decl_try_from_i128 {
    // Wide storage. `i128` always widens losslessly into
    // the storage; the only failure mode is the `checked_mul` by the
    // multiplier overflowing the (still finite) wide storage.
    (wide $Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<i128> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: i128) -> ::core::result::Result<Self, Self::Error> {
                let widened: $Storage = $crate::wide_int::wide_cast(value);
                let scaled = widened
                    .checked_mul(Self::multiplier())
                    .ok_or($crate::error::ConvertError::Overflow)?;
                ::core::result::Result::Ok(Self(scaled))
            }
        }
    };

    // Native (primitive integer) storage.
    ($Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<i128> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: i128) -> ::core::result::Result<Self, Self::Error> {
                let m: i128 = Self::multiplier() as i128;
                let scaled = value
                    .checked_mul(m)
                    .ok_or($crate::error::ConvertError::Overflow)?;
                if scaled > <$Storage>::MAX as i128 || scaled < <$Storage>::MIN as i128 {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::Overflow,
                    );
                }
                ::core::result::Result::Ok(Self(scaled as $Storage))
            }
        }
    };
}

pub(crate) use decl_try_from_i128;

/// Emits `TryFrom<u128> for $Type<SCALE>`. First narrows u128 to i128
/// (rejecting if the value exceeds `i128::MAX`) then delegates to the
/// i128 path.
macro_rules! decl_try_from_u128 {
    // Wide storage. `u128` always widens losslessly into
    // the (256-bit-or-wider) signed storage — including values above
    // `i128::MAX`, which the native path would have to reject — so the
    // only failure mode is the `checked_mul` overflowing the storage.
    (wide $Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<u128> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: u128) -> ::core::result::Result<Self, Self::Error> {
                let widened: $Storage = <$Storage>::from_u128(value);
                let scaled = widened
                    .checked_mul(Self::multiplier())
                    .ok_or($crate::error::ConvertError::Overflow)?;
                ::core::result::Result::Ok(Self(scaled))
            }
        }
    };

    // Native (primitive integer) storage.
    ($Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<u128> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: u128) -> ::core::result::Result<Self, Self::Error> {
                let as_i128: i128 = i128::try_from(value)
                    .map_err(|_| $crate::error::ConvertError::Overflow)?;
                <Self as ::core::convert::TryFrom<i128>>::try_from(as_i128)
            }
        }
    };
}

pub(crate) use decl_try_from_u128;

/// Emits `TryFrom<f64> for $Type<SCALE>`. NaN / ±inf return
/// `NotFinite`; finite values whose scaled magnitude exceeds the
/// storage range return `Overflow`. Truncates toward zero (matches the
/// historical D38 behaviour). For rounding-mode-aware float
/// construction, use `from_f64_with`.
macro_rules! decl_try_from_f64 {
    // Wide storage. The multiplier and storage bounds
    // round-trip through `f64` via the `WideInt` cast; the final
    // `f64` -> wide cast is also the `WideInt` cast.
    (wide $Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<f64> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: f64) -> ::core::result::Result<Self, Self::Error> {
                if !value.is_finite() {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::NotFinite,
                    );
                }
                let mult_f64: f64 = Self::multiplier().as_f64();
                let scaled = value * mult_f64;
                let storage_max_f64: f64 = <$Storage>::MAX.as_f64();
                let storage_min_f64: f64 = <$Storage>::MIN.as_f64();
                if !(storage_min_f64..storage_max_f64).contains(&scaled) {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::Overflow,
                    );
                }
                ::core::result::Result::Ok(Self(<$Storage>::from_f64(scaled)))
            }
        }
    };

    // Native (primitive integer) storage.
    ($Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<f64> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: f64) -> ::core::result::Result<Self, Self::Error> {
                if !value.is_finite() {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::NotFinite,
                    );
                }
                let scaled = value * (Self::multiplier() as f64);
                let storage_max_f64 = <$Storage>::MAX as f64;
                let storage_min_f64 = <$Storage>::MIN as f64;
                if !(storage_min_f64..storage_max_f64).contains(&scaled) {
                    return ::core::result::Result::Err(
                        $crate::error::ConvertError::Overflow,
                    );
                }
                ::core::result::Result::Ok(Self(scaled as $Storage))
            }
        }
    };
}

/// Emits `TryFrom<f32> for $Type<SCALE>` by delegating to the
/// `TryFrom<f64>` path via a widening cast. The body is storage-
/// agnostic — it only forwards — so a single arm serves every width;
/// the `wide` token is accepted and ignored for call-site symmetry
/// with the other conversion macros.
macro_rules! decl_try_from_f32 {
    (wide $Type:ident, $Storage:ty) => {
        $crate::macros::conversions::decl_try_from_f32!($Type, $Storage);
    };
    ($Type:ident, $Storage:ty) => {
        impl<const SCALE: u32> ::core::convert::TryFrom<f32> for $Type<SCALE> {
            type Error = $crate::error::ConvertError;
            #[inline]
            fn try_from(value: f32) -> ::core::result::Result<Self, Self::Error> {
                <Self as ::core::convert::TryFrom<f64>>::try_from(value as f64)
            }
        }
    };
}

pub(crate) use decl_try_from_f32;
pub(crate) use decl_try_from_f64;

/// Emits the named integer constructors and `to_int` /
/// `to_int_with` on a decimal type. `$Storage` is the storage
/// integer; `$IntSrc` is the wider integer source for `from_int`
/// (typically `i64` for D18/D38 and `i32` for D9). `from_int` and
/// `from_i32` scale directly (they do not depend on a `From<iN>` impl
/// existing for the width).
macro_rules! decl_decimal_int_conversion_methods {
    // Wide storage. The rounding logic mirrors the native
    // arm but is carried in the wide storage type throughout; the
    // `i128` source widens via the `WideInt` cast, and the final
    // saturating narrow to `i64` also goes through the `WideInt` cast.
    (wide $Type:ident, $Storage:ty, $IntSrc:ty) => {
        impl<const SCALE: u32> $Type<SCALE> {
            /// Constructs from an integer source, scaling by `10^SCALE`.
            /// Overflow follows the wide integer's default arithmetic semantics.
            #[inline]
            pub fn from_int(value: $IntSrc) -> Self {
                let widened: $Storage = $crate::wide_int::wide_cast(value as i128);
                Self(widened * Self::multiplier())
            }

            /// Constructs from an `i32`, scaling by `10^SCALE`.
            #[inline]
            pub fn from_i32(value: i32) -> Self {
                let widened: $Storage = $crate::wide_int::wide_cast(value as i128);
                Self(widened * Self::multiplier())
            }

            /// Converts to `i64` using the crate default rounding mode.
            /// Saturates to `i64::MAX` / `i64::MIN` when the rounded
            /// integer part falls outside `i64`'s range.
            #[inline]
            pub fn to_int(self) -> i64 {
                self.to_int_with($crate::rounding::DEFAULT_ROUNDING_MODE)
            }

            /// Converts to `i64` using the supplied rounding mode for the
            /// fractional discard step. Saturates to `i64::MAX` /
            /// `i64::MIN` when the rounded integer is out of `i64` range.
            #[inline]
            pub fn to_int_with(
                self,
                mode: $crate::rounding::RoundingMode,
            ) -> i64 {
                let zero = <$Storage>::from_str_radix("0", 10)
                    .expect("wide decimal: invalid base-10 literal");
                let one = <$Storage>::from_str_radix("1", 10)
                    .expect("wide decimal: invalid base-10 literal");
                let raw = self.0;
                let divisor = Self::multiplier();
                let quotient = raw / divisor;
                let remainder = raw % divisor;
                let int_rounded: $Storage = if remainder == zero {
                    quotient
                } else {
                    let abs_rem = remainder.unsigned_abs();
                    // `divisor` is `10^SCALE` and always positive, so
                    // `unsigned_abs()` is the value itself; `>> 1` is
                    // the half-LSB threshold.
                    let half = divisor.unsigned_abs() >> 1;
                    let non_negative = !raw.is_negative();
                    match mode {
                        $crate::rounding::RoundingMode::HalfToEven => {
                            if abs_rem < half {
                                quotient
                            } else if abs_rem > half {
                                if non_negative { quotient + one } else { quotient - one }
                            } else if !quotient.bit(0) {
                                quotient
                            } else if non_negative {
                                quotient + one
                            } else {
                                quotient - one
                            }
                        }
                        $crate::rounding::RoundingMode::HalfAwayFromZero => {
                            if abs_rem < half {
                                quotient
                            } else if non_negative {
                                quotient + one
                            } else {
                                quotient - one
                            }
                        }
                        $crate::rounding::RoundingMode::HalfTowardZero => {
                            if abs_rem > half {
                                if non_negative { quotient + one } else { quotient - one }
                            } else {
                                quotient
                            }
                        }
                        $crate::rounding::RoundingMode::Trunc => quotient,
                        $crate::rounding::RoundingMode::Floor => {
                            if non_negative { quotient } else { quotient - one }
                        }
                        $crate::rounding::RoundingMode::Ceiling => {
                            if non_negative { quotient + one } else { quotient }
                        }
                    }
                };
                let i64_max: $Storage = $crate::wide_int::wide_cast(i64::MAX);
                let i64_min: $Storage = $crate::wide_int::wide_cast(i64::MIN);
                if int_rounded > i64_max {
                    i64::MAX
                } else if int_rounded < i64_min {
                    i64::MIN
                } else {
                    $crate::wide_int::wide_cast::<_, i64>(int_rounded)
                }
            }
        }
    };

    // Native (primitive integer) storage.
    ($Type:ident, $Storage:ty, $IntSrc:ty) => {
        impl<const SCALE: u32> $Type<SCALE> {
            /// Constructs from an integer at the widest supported source,
            /// scaling by `10^SCALE`. Overflow follows Rust's default
            /// integer arithmetic (debug panic, release wrap).
            #[inline]
            pub fn from_int(value: $IntSrc) -> Self {
                Self((value as $Storage) * Self::multiplier())
            }

            /// Constructs from an `i32`, scaling by `10^SCALE`.
            #[inline]
            pub fn from_i32(value: i32) -> Self {
                Self((value as $Storage) * Self::multiplier())
            }

            /// Converts to `i64` using the crate default rounding mode.
            /// Saturates to `i64::MAX` / `i64::MIN` when the integer part
            /// of the rounded value falls outside `i64`'s range.
            #[inline]
            pub fn to_int(self) -> i64 {
                self.to_int_with($crate::rounding::DEFAULT_ROUNDING_MODE)
            }

            /// Converts to `i64` using the supplied rounding mode for the
            /// fractional discard step. Saturates to `i64::MAX` /
            /// `i64::MIN` when the rounded integer is out of `i64` range.
            #[inline]
            pub fn to_int_with(
                self,
                mode: $crate::rounding::RoundingMode,
            ) -> i64 {
                let raw = self.0 as i128;
                let divisor = Self::multiplier() as i128;
                let quotient = raw / divisor;
                let remainder = raw % divisor;
                let int_rounded: i128 = if remainder == 0 {
                    quotient
                } else {
                    let abs_rem = remainder.unsigned_abs();
                    let half = (divisor / 2) as u128;
                    match mode {
                        $crate::rounding::RoundingMode::HalfToEven => {
                            if abs_rem < half {
                                quotient
                            } else if abs_rem > half {
                                if raw >= 0 { quotient + 1 } else { quotient - 1 }
                            } else if quotient % 2 == 0 {
                                quotient
                            } else if raw >= 0 {
                                quotient + 1
                            } else {
                                quotient - 1
                            }
                        }
                        $crate::rounding::RoundingMode::HalfAwayFromZero => {
                            if abs_rem < half {
                                quotient
                            } else if raw >= 0 {
                                quotient + 1
                            } else {
                                quotient - 1
                            }
                        }
                        $crate::rounding::RoundingMode::HalfTowardZero => {
                            if abs_rem > half {
                                if raw >= 0 { quotient + 1 } else { quotient - 1 }
                            } else {
                                quotient
                            }
                        }
                        $crate::rounding::RoundingMode::Trunc => quotient,
                        $crate::rounding::RoundingMode::Floor => {
                            if raw >= 0 { quotient } else { quotient - 1 }
                        }
                        $crate::rounding::RoundingMode::Ceiling => {
                            if raw >= 0 { quotient + 1 } else { quotient }
                        }
                    }
                };
                if int_rounded > i64::MAX as i128 {
                    i64::MAX
                } else if int_rounded < i64::MIN as i128 {
                    i64::MIN
                } else {
                    int_rounded as i64
                }
            }
        }
    };
}

pub(crate) use decl_decimal_int_conversion_methods;