The d*! macros¶
The crate exposes one proc-macro per public width - d9!, d18!,
d38!, plus d76! / d153! / d307! under the wide-tier feature
flags. 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, D38s2::from_bits(123));
# assert_eq!(b, D38s0::from_bits(123));
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, D38s0::from_bits(1500));
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, D38s2::from_bits(123));
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, D38s0::from_bits(123));
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 (D9 … 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. core_type.rs is then largely a list of macro invocations, one
group per width:
rust,ignore
crate::macros::basics::decl_decimal_basics!(D38, i128, 38);
crate::macros::arithmetic::decl_decimal_arithmetic!(D18, i64, i128);
crate::macros::basics::decl_decimal_basics!(wide D76, crate::wide::I256, 76);
Most macros have two front-end arms:
- a native arm for primitive-integer storage (
i32/i64/i128), which usesas-casts and integer literals; - a
widearm for the in-tree wide-integer storage (D76/D153/D307), which has noas-casts from literals - it builds constants viafrom_str_radixand widens viacrate::wide_int::wide_cast.
Both arms forward 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.