The d*! macros¶
The crate exposes one proc-macro per public width — d18! and d38!
always, and d57! / d76! / d115! / d153! / d230! / d307! /
d462! / d616! / d924! / d1232! under the wide-tier feature flags
(twelve in all, one per tier). Each parses a numeric literal (or inline
expression),
picks or is told a scale, and expands to a
D<N><SCALE>::from_bits(...) call. There is no runtime parsing and
the scale lands in the type.
This page focuses on d38! because most examples in the crate use
the 128-bit tier; everything below applies identically to the other
entry points (substitute MAX_SCALE per-width). The full
specification - including radix-prefix integers and the curated
per-scale wrappers (d38s12!, d18s6!, …) - lives in
macros/README.md.
Enable the macros with the macros feature (off by default):
Automatic scale inference¶
With no qualifier, the scale is the number of fractional digits you wrote - trailing zeros are significant:
# use decimal_scaled::d38;
# use decimal_scaled::{D38, D38s0, D38s2, D38s5};
let a = d38!(1.23); // D38<2>
let b = d38!(123); // D38<0>
let c = d38!(1.0); // D38<1> - the written `.0` counts
let d = d38!(1.230); // D38<3> - trailing zero preserved
let e = d38!(0.001); // D38<3>
let f = d38!(-1.23); // D38<2>
let g = d38!(1_234.567_89); // D38<5> - underscores allowed
# assert_eq!(a.to_bits(), 123i128);
# assert_eq!(b.to_bits(), 123i128);
Scientific notation¶
e notation is supported; the resulting scale is whatever is needed to
represent the value exactly:
# use decimal_scaled::d38;
# use decimal_scaled::{D38, D38s0};
let a = d38!(1.5e3); // 1500 -> D38<0>
let b = d38!(1.5e-3); // 0.0015 -> D38<4>
let c = d38!(1e6); // 1000000 -> D38<0>
let d = d38!(-2.5e-2); // -0.025 -> D38<3>
# assert_eq!(a.to_bits(), 1500i128);
The scale N qualifier¶
Force a specific target scale. Scaling up pads with zeros (lossless):
# use decimal_scaled::d38;
# use decimal_scaled::D38;
let a = d38!(1.23, scale 4); // D38<4>, raw 12_300
let b = d38!(42, scale 0); // D38<0>, raw 42
let c = d38!(1.5e3, scale 5); // D38<5>, raw 150_000_000
Scaling down with scale N alone is a compile error if it would lose
digits - add rounded to opt in.
The rounded qualifier¶
Combine with scale N to round (half-to-even) when the target scale has
fewer digits than the literal:
# use decimal_scaled::d38;
# use decimal_scaled::D38s2;
let a = d38!(1.234999, scale 2, rounded); // 1.23
let b = d38!(1.235001, scale 2, rounded); // 1.24
let c = d38!(1.235, scale 2, rounded); // 1.24 (tie -> even)
let d = d38!(1.225, scale 2, rounded); // 1.22 (tie -> even)
# assert_eq!(a.to_bits(), 123i128);
Inline expressions¶
The first argument can be an integer expression; combine with
scale N to land it in a typed D38:
# use decimal_scaled::d38;
# use decimal_scaled::{D38, D38s0};
let a = d38!(10 * 12 + 3, scale 0); // D38<0>, raw 123
let b = d38!(5, scale 4); // D38<4>, raw 50_000
# assert_eq!(a.to_bits(), 123i128);
How the type family is generated - the decl_*! macros¶
This section is for contributors, not users of the crate.
Hand-writing six near-identical impl surfaces (D18 … D307) would be
unmaintainable, so the per-width surface is generated by a family of
internal declarative macros in src/macros/. Each file owns one
surface - arithmetic.rs, basics.rs, conversions.rs, display.rs,
overflow.rs, rescale.rs, … - and exports one decl_decimal_*!
macro. src/types/widths.rs is then largely a list of macro
invocations, one group per width:
rust,ignore
crate::macros::basics::decl_decimal_basics!(wide D38, crate::int::types::Int<2>, 37);
crate::macros::basics::decl_decimal_basics!(wide D18, crate::int::types::Int<1>, 17);
crate::macros::basics::decl_decimal_basics!(wide D76, crate::int::types::Int<4>, 75);
The storage backend is always the const-generic Int<N> (every tier,
including D18 = Int<1> and D38 = Int<2>), and the MAX_SCALE
literal is name - 1 (D76 → 75).
Each macro now has a single front-end wide arm taking the
Int<N> storage; it builds constants via the Int conversion surface
rather than as-casts. That arm forwards to a shared @impl /
@common arm wherever the logic is genuinely identical, so each
operation is written once.
The discipline: anything uniform across widths lives in a macro;
anything genuinely width-specific (e.g. D38's hand-rolled
mg_divide multiply/divide path) stays hand-coded. See
macros/README.md for the full specification.