Skip to content

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):

[dependencies]
decimal-scaled = { version = "0.3.1", features = ["macros"] }
use decimal_scaled::d38;

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 (D9D307) 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 uses as-casts and integer literals;
  • a wide arm for the in-tree wide-integer storage (D76 / D153 / D307), which has no as-casts from literals - it builds constants via from_str_radix and widens via crate::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.