Skip to main content

decimal_scaled/
lib.rs

1//! Const-generic base-10 fixed-point decimal types for deterministic arithmetic.
2//!
3//! # Overview
4//!
5//! `decimal-scaled` provides a family of fixed-point decimal types whose stored
6//! integer encodes `actual_value * 10^SCALE`. Decimal literals like `1.1`
7//! round-trip exactly without any binary approximation, and all core arithmetic
8//! is integer-only — identical bit-patterns on every platform.
9//!
10//! # Primary types
11//!
12//! Each width has a `D<digits><const SCALE: u32>` const-generic shape with the
13//! same method surface; pick the narrowest that fits your range. The number on
14//! every `D{N}` type is `MAX_SCALE` — the highest `SCALE` the storage can hold.
15//!
16//! | Type | Storage | `MAX_SCALE` | Feature gate |
17//! |------|---------|-------------|--------------|
18//! | [`D9<SCALE>`]    | `i32`     |    9 | always on |
19//! | [`D18<SCALE>`]   | `i64`     |   18 | always on |
20//! | [`D38<SCALE>`]   | `i128`    |   38 | always on |
21//! | [`D57<SCALE>`]   | 192-bit   |   57 | `d57` or `wide` |
22//! | [`D76<SCALE>`]   | 256-bit   |   76 | `d76` or `wide` |
23//! | [`D115<SCALE>`]  | 384-bit   |  115 | `d115` or `wide` |
24//! | [`D153<SCALE>`]  | 512-bit   |  153 | `d153` or `wide` |
25//! | [`D230<SCALE>`]  | 768-bit   |  230 | `d230` or `wide` |
26//! | [`D307<SCALE>`]  | 1024-bit  |  307 | `d307` or `wide` |
27//! | [`D462<SCALE>`]  | 1536-bit  |  462 | `d462` or `x-wide` |
28//! | [`D616<SCALE>`]  | 2048-bit  |  616 | `d616` or `x-wide` |
29//! | [`D924<SCALE>`]  | 3072-bit  |  924 | `d924` or `xx-wide` |
30//! | [`D1232<SCALE>`] | 4096-bit  | 1232 | `d1232` or `xx-wide` |
31//!
32//! Umbrellas: `wide` enables D57 / D76 / D115 / D153 / D230 / D307;
33//! `x-wide` adds D462 + D616; `xx-wide` adds D924 + D1232. Every
34//! adjacent pair has lossless `.widen()` / fallible `.narrow()`
35//! helpers plus `From` / `TryFrom` impls.
36//!
37//! Concrete scale aliases such as `D38s12 = D38<12>` are emitted for every
38//! supported `SCALE`. `SCALE = MAX_SCALE + 1` is rejected at compile time —
39//! `10^(MAX_SCALE+1)` overflows the storage type.
40//!
41//! The width-generic [`Decimal`] trait carries the surface that is identical
42//! across widths (constants, arithmetic operators, sign methods, integer
43//! variants, pow / checked / wrapping / saturating / overflowing, float bridge,
44//! Euclidean / floor / ceil division, etc.). Use it to write helpers that work
45//! across widths; reach for the concrete type for width-specific operations
46//! like `rescale::<TARGET>()` whose const-generic parameter cannot live on a
47//! trait method.
48//!
49//! # Equality and hashing
50//!
51//! Because each logical value has exactly one representation at a fixed scale,
52//! `Hash`, `Eq`, `PartialEq`, `PartialOrd`, and `Ord` are all derived from the
53//! underlying integer storage. Two `Dxx<S>` values compare equal if and only
54//! if their raw bit patterns are identical. This gives predictable behaviour
55//! when decimal values are used as `HashMap` keys, unlike variable-scale
56//! decimal types where `1.10` and `1.1` may hash differently.
57//!
58//! # `num-traits` compatibility
59//!
60//! Every width implements the standard `num-traits` 0.2 surface:
61//! `Zero`, `One`, `Num`, `Bounded`, `Signed`, `FromPrimitive`,
62//! `ToPrimitive`, and the `Checked{Add,Sub,Mul,Div,Rem,Neg}` family
63//! (see [`::num_traits`]). These impls are unconditional (not behind a
64//! feature flag) because generic numeric code in the wider ecosystem
65//! consumes this surface by default.
66//!
67//! # `no_std` support
68//!
69//! The crate compiles with `no_std + alloc` when default features are
70//! disabled. `alloc` is required for `Display::to_string` and
71//! `FromStr::from_str`. Targets without `alloc` are not supported.
72//!
73//! # Feature flags
74//!
75//! - `std` (default): enables the fast implementations of transcendental
76//! functions (trigonometry, logarithms, exponentials, square root, cube
77//! root, float power) that delegate to platform `f64` intrinsics.
78//! - `alloc`: pulled in automatically; required for string formatting and
79//! parsing.
80//! - `serde`: enables `serde_helpers` for serialisation and deserialisation.
81//! - `strict`: enables integer-only implementations of all transcendental
82//! functions. When `strict` is active each function that would otherwise
83//! route through `f64` is instead implemented using integer-only
84//! algorithms. Explicit float-conversion methods (`to_f64`,
85//! `from_f64`, etc.) remain available regardless; they are type
86//! conversions, not mathematical operations. `strict` does not require
87//! `std`; the integer transcendental implementations compile under
88//! `no_std + alloc`.
89
90#![cfg_attr(not(feature = "std"), no_std)]
91#![cfg_attr(feature = "experimental-floats", feature(f16, f128))]
92#![cfg_attr(feature = "cross-scale-ops", feature(generic_const_exprs))]
93#![cfg_attr(feature = "cross-scale-ops", allow(incomplete_features))]
94// ── Clippy allow-list ─────────────────────────────────────────────────
95//
96// These are pedantic lints whose patterns this crate uses
97// intentionally and pervasively. Each is justified inline; allowing
98// them at the crate level is preferable to spraying per-site
99// `#[allow]` attributes or rewriting against the crate's domain.
100#![allow(
101    // Decimal width names overlap with type prefixes; the lint adds no
102    // signal here.
103    clippy::module_name_repetitions,
104    // We use unindented Markdown continuation in module docs.
105    clippy::doc_lazy_continuation,
106    // We routinely place a blank line between a method's `#[cfg]`
107    // attribute and its doc/body for readability.
108    clippy::empty_line_after_outer_attr,
109    // Big-integer arithmetic regularly casts between signed/unsigned
110    // and between widths. The wraps / truncations / sign flips are
111    // intentional — `unsigned_abs` paths, two's-complement tricks,
112    // narrowing the final result back to storage after a widened mul.
113    clippy::cast_possible_truncation,
114    clippy::cast_possible_wrap,
115    clippy::cast_sign_loss,
116    // We prefer `as` casts over `T::from(x)` in arithmetic-heavy
117    // inner loops for readability and to match the surrounding
118    // big-integer idiom.
119    clippy::cast_lossless,
120    // Float bridges (`to_f64`, `to_f32`) are explicitly lossy by
121    // contract. The lint is a tautology here.
122    clippy::cast_precision_loss,
123    // Literals like `1_000_000_000_000` carry the scale visually and
124    // are kept unseparated when they encode `10^SCALE`.
125    clippy::unreadable_literal,
126    // `if cond { panic!(…) }` is the crate's canonical bounds-check
127    // shape; `assert!(…)` would lose the dynamic message.
128    clippy::manual_assert,
129    // `Result<_, ()>` is the only honest error type for `const fn`
130    // digit-validity checks where no allocator is available.
131    clippy::result_unit_err,
132    // `if …; if …` chains read more cleanly than `if … && …` in the
133    // const-fn limb-arithmetic helpers.
134    clippy::collapsible_if,
135    // Big-int / fixed-point inner loops use `i`, `j`, `k`, `n`, `m`
136    // as conventional names. Renaming to `outer_index` etc. hurts
137    // readability without payoff.
138    clippy::similar_names,
139    clippy::many_single_char_names,
140    // Strict-transcendental kernels exceed 100 lines because they
141    // unroll a series-evaluation loop; splitting them just to please
142    // the line-count lint would scatter the algorithm.
143    clippy::too_many_lines,
144    // `#[inline(always)]` is set deliberately on small hot-path
145    // helpers (`apply_rounding`, `panic_or_wrap_*`). The lint
146    // assumes the inliner knows better; here we override on purpose.
147    clippy::inline_always,
148    // Strict-vs-fast comparisons in `tests/` deliberately compare
149    // raw `f64` results bit-for-bit. The lint can't tell test code
150    // from production.
151    clippy::float_cmp,
152    // Some narrow helpers `let result = …; result + 1` are flagged
153    // as let-else candidates; the explicit form is clearer in the
154    // big-int helpers.
155    clippy::manual_let_else,
156    // `format!("{x}") + "y"` is fine when both pieces stay tiny.
157    clippy::format_push_string,
158    // `if-else-if` chains over disjoint conditions sometimes read
159    // more clearly than `match` (especially with `<` / `>=` arms).
160    clippy::comparison_chain,
161    // Macro-emitted methods that return `Self` are wrapped with
162    // `#[must_use]` where it would catch bugs; the lint's
163    // recommendation on tiny constructors is noise.
164    clippy::must_use_candidate,
165    clippy::return_self_not_must_use,
166    // `# Errors` / `# Panics` sections: every public function's
167    // behaviour on error / panic is described in its main doc
168    // paragraph (and matches the pattern of the std-library
169    // primitive it shadows). The lint's per-section requirement
170    // adds boilerplate without information.
171    clippy::missing_errors_doc,
172    clippy::missing_panics_doc,
173    // Doc-comment backticks are added where they matter (type and
174    // function names); the lint flags every identifier-looking
175    // word, including math symbols and abbreviations.
176    clippy::doc_markdown,
177)]
178
179#[cfg(feature = "alloc")]
180extern crate alloc;
181
182// Re-export `tracing` under the crate so the perf-trace cfg-gated
183// `info_span!` calls in macro-emitted modules can reach it via
184// `$crate::tracing::…`. Internal-only — gated by the same feature.
185#[cfg(feature = "perf-trace")]
186#[doc(hidden)]
187pub use ::tracing;
188
189mod algos;
190mod identity;
191mod support;
192mod types;
193#[cfg(feature = "bench-alt")]
194#[doc(hidden)]
195pub mod __bench_internals {
196    #[inline(never)]
197    pub fn limbs_mul(a: &[u128], b: &[u128], out: &mut [u128]) {
198        crate::wide_int::limbs_mul(a, b, out)
199    }
200    #[inline(never)]
201    pub fn limbs_mul_fast(a: &[u128], b: &[u128], out: &mut [u128]) {
202        crate::wide_int::limbs_mul_fast(a, b, out)
203    }
204    #[inline(never)]
205    pub fn mul_slice(a: &[u64], b: &[u64], out: &mut [u64]) {
206        crate::wide_int::limbs_mul_u64(a, b, out)
207    }
208    #[inline(never)]
209    pub fn mul_fixed<const L: usize, const D: usize>(
210        a: &[u64; L],
211        b: &[u64; L],
212        out: &mut [u64; D],
213    ) {
214        crate::wide_int::limbs_mul_u64_fixed::<L, D>(a, b, out)
215    }
216    #[inline(never)]
217    pub fn mul_u64_into<const L: usize, const LP1: usize>(
218        a: &[u64; L],
219        n: u64,
220        out: &mut [u64; LP1],
221    ) {
222        crate::wide_int::limbs_mul_u64_into::<L, LP1>(a, n, out)
223    }
224
225    // Newton-reciprocal divide research kernel — wrapped via concrete
226    // shims so the bench harness gets head-to-head comparisons against
227    // [`crate::algos::mg_divide::div_wide_pow10_chain_with`] without
228    // exposing trait machinery.
229    #[cfg(any(feature = "x-wide", feature = "xx-wide"))]
230    pub mod newton_vs_mg {
231        use crate::algos::newton_reciprocal::NewtonReciprocal as NR;
232        pub struct NewtonReciprocal(pub(crate) NR);
233        impl NewtonReciprocal {
234            #[inline(never)]
235            pub fn precompute(scale: u32, width_limbs: usize) -> Self {
236                Self(NR::precompute(scale, width_limbs))
237            }
238        }
239
240        macro_rules! shim {
241            ($pub_name:ident, $width:ty, $feat:literal) => {
242                #[cfg(any(feature = $feat))]
243                pub mod $pub_name {
244                    use super::NewtonReciprocal;
245                    use crate::RoundingMode;
246                    use $width as W;
247
248                    /// Storage type for this tier — opaque to the bench.
249                    #[derive(Clone, Copy)]
250                    pub struct Storage(W);
251
252                    /// Build a representative non-zero numerator from a top-limb position.
253                    #[inline(never)]
254                    pub fn build_numerator(top_limb_idx: usize) -> Storage {
255                        use crate::wide_int::WideInt;
256                        let mut mag = [0u128; 64];
257                        mag[top_limb_idx] = 1u128 << 32;
258                        mag[1] = 0xdeadbeef_cafef00d_u128;
259                        Storage(W::from_mag_sign_u128(&mag, false))
260                    }
261
262                    #[inline(never)]
263                    pub fn mg_chain(n: Storage, scale: u32) -> Storage {
264                        Storage(crate::algos::mg_divide::div_wide_pow10_chain_with::<
265                            W,
266                            { <W as crate::wide_int::WideInt>::U128_LIMBS },
267                        >(
268                            n.0,
269                            scale,
270                            RoundingMode::HalfToEven,
271                        ))
272                    }
273
274                    #[inline(never)]
275                    pub fn mg_single(n: Storage, scale: u32) -> Storage {
276                        Storage(crate::algos::mg_divide::div_wide_pow10_with::<
277                            W,
278                            { <W as crate::wide_int::WideInt>::U128_LIMBS },
279                        >(
280                            n.0,
281                            scale,
282                            RoundingMode::HalfToEven,
283                        ))
284                    }
285
286                    #[inline(never)]
287                    pub fn newton(
288                        n: Storage,
289                        scale: u32,
290                        table: &NewtonReciprocal,
291                    ) -> Storage {
292                        Storage(crate::algos::newton_reciprocal::div_wide_pow10_newton_with::<W>(
293                            n.0,
294                            scale,
295                            RoundingMode::HalfToEven,
296                            &table.0,
297                        ))
298                    }
299                }
300            };
301        }
302        shim!(d307, crate::wide_int::I1024, "x-wide");
303        shim!(d616, crate::wide_int::I2048, "x-wide");
304        shim!(d924, crate::wide_int::I3072, "xx-wide");
305        shim!(d1232, crate::wide_int::I4096, "xx-wide");
306    }
307}
308mod macros;
309
310#[cfg(feature = "cross-scale-ops")]
311pub mod cross_scale;
312
313/// Nightly-gated auto-inferred cross-scale operations. See
314/// [`mod@cross_scale`] for details.
315#[cfg(feature = "cross-scale-ops")]
316pub use crate::cross_scale as cross;
317
318// `bitwise` and `num_traits_impls` used to live here as test-only
319// modules; their tests now run as Cargo integration tests under
320// `tests/`. The macro-generated impls themselves are emitted by
321// `decl_decimal_bitwise!` / `decl_decimal_num_traits_basics!` from
322// `types/widths.rs`, alongside every other surface.
323//
324// `wide_int` is unconditional. D38's strict transcendentals use
325// `Int512` as their guard-digit work integer (replacing the previous
326// `algos::fixed_d38::Fixed` 256-bit sign-magnitude type), so the wide-
327// integer family must be available in every feature configuration —
328// not just `feature = "wide"` builds. Compile-time impact is modest:
329// ~2k LOC of self-contained limb arithmetic plus the per-width
330// `decl_wide_int!` instantiations.
331mod wide_int;
332mod policy;
333
334#[cfg(feature = "serde")]
335pub use crate::support::serde_helpers;
336
337
338pub use crate::types::consts::DecimalConstants;
339pub use crate::types::traits::DecimalArithmetic;
340pub use crate::types::traits::DecimalConvert;
341pub use crate::types::unified::D;
342pub use crate::types::traits::Decimal;
343pub use crate::support::error::{ConvertError, ParseError};
344pub use crate::support::rounding::RoundingMode;
345pub use crate::types::traits::DecimalTranscendental;
346pub use crate::types::traits::WidthLE;
347
348#[cfg(feature = "dyn")]
349pub use crate::types::traits::dyn_decimal::{DecimalWidth, DynDecimal, RawStorage};
350
351// D38 — the 128-bit foundation, plus every scale alias D38s0..=D38s37
352// (v0.4.0 cap: MAX_SCALE = name - 1).
353pub use crate::types::widths::{
354    D38, D38s0, D38s1, D38s2, D38s3, D38s4, D38s5, D38s6, D38s7, D38s8, D38s9, D38s10,
355    D38s11, D38s12, D38s13, D38s14, D38s15, D38s16, D38s17, D38s18, D38s19, D38s20,
356    D38s21, D38s22, D38s23, D38s24, D38s25, D38s26, D38s27, D38s28, D38s29, D38s30,
357    D38s31, D38s32, D38s33, D38s34, D38s35, D38s36, D38s37,
358};
359
360// D9 — 32-bit storage, scale 0..=9.
361pub use crate::types::widths::{
362    D9, D9s0, D9s1, D9s2, D9s3, D9s4, D9s5, D9s6, D9s7, D9s8,
363};
364
365// D18 — 64-bit storage, scale 0..=18.
366pub use crate::types::widths::{
367    D18, D18s0, D18s1, D18s2, D18s3, D18s4, D18s5, D18s6, D18s7, D18s8, D18s9, D18s10, D18s11,
368    D18s12, D18s13, D18s14, D18s15, D18s16, D18s17,
369};
370
371// D76 — 256-bit storage, behind the `d76` / `wide` features.
372#[cfg(any(feature = "d76", feature = "wide"))]
373pub use crate::types::widths::{
374    D76,
375    D76s0, D76s1, D76s2, D76s3, D76s4, D76s6, D76s9, D76s12, D76s15,
376    D76s18, D76s20, D76s24, D76s28, D76s32, D76s35, D76s38, D76s42,
377    D76s48, D76s50, D76s56, D76s64, D76s70, D76s75,
378};
379
380// The hand-rolled wide-integer types — the storage backend for the
381// wide decimal tiers, also useful on their own.
382#[cfg(any(feature = "d76", feature = "d153", feature = "d307", feature = "wide"))]
383pub use wide_int::{
384    Int256, Int512, Int1024, Int2048, Int4096, Uint256, Uint512, Uint1024, Uint2048, Uint4096,
385};
386
387// D153 — 512-bit storage, behind the `d153` / `wide` features.
388#[cfg(any(feature = "d153", feature = "wide"))]
389pub use crate::types::widths::{
390    D153,
391    D153s0, D153s1, D153s2, D153s4, D153s6, D153s9, D153s12, D153s15,
392    D153s18, D153s20, D153s24, D153s28, D153s32, D153s35, D153s38,
393    D153s50, D153s57, D153s75, D153s76, D153s100, D153s115, D153s140,
394    D153s150, D153s152,
395};
396
397// D307 — 1024-bit storage, behind the `d307` / `wide` features.
398#[cfg(any(feature = "d307", feature = "wide"))]
399pub use crate::types::widths::{
400    D307,
401    D307s0, D307s1, D307s2, D307s4, D307s6, D307s9, D307s12, D307s15,
402    D307s18, D307s20, D307s24, D307s28, D307s32, D307s35, D307s38,
403    D307s50, D307s75, D307s100, D307s115, D307s150, D307s153,
404    D307s200, D307s230, D307s275, D307s300, D307s306,
405};
406
407// ─── New half-width and wider tiers ───────────────────────────────────
408
409// D57 — 192-bit storage; half-width between D38 and D76.
410#[cfg(any(feature = "d57", feature = "wide"))]
411pub use crate::types::widths::{
412    D57,
413    D57s0, D57s1, D57s2, D57s4, D57s6, D57s9, D57s12, D57s18, D57s20, D57s24,
414    D57s28, D57s32, D57s38, D57s42, D57s48, D57s52, D57s56,
415};
416#[cfg(any(feature = "d57", feature = "wide"))]
417pub use wide_int::{Int192, Uint192};
418
419// D115 — 384-bit; half-width between D76 and D153.
420#[cfg(any(feature = "d115", feature = "wide"))]
421pub use crate::types::widths::{
422    D115,
423    D115s0, D115s1, D115s4, D115s8, D115s16, D115s24, D115s32, D115s38, D115s50,
424    D115s57, D115s64, D115s76, D115s90, D115s100, D115s110, D115s114,
425};
426#[cfg(any(feature = "d115", feature = "wide"))]
427pub use wide_int::{Int384, Uint384};
428
429// D230 — 768-bit; half-width between D153 and D307.
430#[cfg(any(feature = "d230", feature = "wide"))]
431pub use crate::types::widths::{
432    D230,
433    D230s0, D230s1, D230s6, D230s18, D230s38, D230s57, D230s75, D230s100, D230s115,
434    D230s140, D230s153, D230s175, D230s200, D230s215, D230s225, D230s229,
435};
436#[cfg(any(feature = "d230", feature = "wide"))]
437pub use wide_int::{Int768, Uint768};
438
439// D462 — 1536-bit; half-width between D307 and D616.
440#[cfg(any(feature = "d462", feature = "x-wide"))]
441pub use crate::types::widths::{
442    D462,
443    D462s0, D462s1, D462s18, D462s38, D462s75, D462s115, D462s153, D462s200, D462s230,
444    D462s275, D462s307, D462s350, D462s400, D462s440, D462s460, D462s461,
445};
446#[cfg(any(feature = "d462", feature = "x-wide"))]
447pub use wide_int::{Int1536, Uint1536};
448
449// D616 — 2048-bit; new top wide tier. Int2048 / Uint2048 are
450// already exported above for x-wide / d307 widening; no re-export
451// here.
452#[cfg(any(feature = "d616", feature = "x-wide"))]
453pub use crate::types::widths::{
454    D616,
455    D616s0, D616s1, D616s38, D616s75, D616s115, D616s153, D616s200, D616s230, D616s275,
456    D616s308, D616s380, D616s462, D616s500, D616s555, D616s600, D616s615,
457};
458
459// D924 — 3072-bit; half-width between D616 and D1232.
460#[cfg(any(feature = "d924", feature = "xx-wide"))]
461pub use crate::types::widths::{
462    D924,
463    D924s0, D924s1, D924s75, D924s153, D924s230, D924s307, D924s400, D924s461, D924s462,
464    D924s500, D924s616, D924s700, D924s800, D924s860, D924s900, D924s920, D924s923,
465};
466#[cfg(any(feature = "d924", feature = "xx-wide"))]
467pub use wide_int::{Int3072, Int6144, Int12288, Uint3072, Uint6144, Uint12288};
468
469// D1232 — 4096-bit; widest tier shipped.
470#[cfg(any(feature = "d1232", feature = "xx-wide"))]
471pub use crate::types::widths::{
472    D1232,
473    D1232s0, D1232s1, D1232s75, D1232s153, D1232s230, D1232s307, D1232s461, D1232s616,
474    D1232s700, D1232s800, D1232s900, D1232s924, D1232s1000, D1232s1100,
475    D1232s1180, D1232s1220, D1232s1230, D1232s1231,
476};
477#[cfg(any(feature = "d1232", feature = "xx-wide"))]
478pub use wide_int::{Int8192, Int16384, Uint8192, Uint16384};
479
480// ─── Construction macros (re-exports + per-scale wrappers) ────────────
481
482/// The narrow-tier proc-macros are always available with the
483/// `macros` feature; the wide-tier proc-macros are additionally
484/// feature-gated to match their target type's availability.
485#[cfg(feature = "macros")]
486pub use decimal_scaled_macros::{d9, d18, d38};
487
488#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))]
489pub use decimal_scaled_macros::d76;
490
491#[cfg(all(feature = "macros", any(feature = "d153", feature = "wide")))]
492pub use decimal_scaled_macros::d153;
493
494#[cfg(all(feature = "macros", any(feature = "d307", feature = "wide", feature = "x-wide")))]
495pub use decimal_scaled_macros::d307;
496
497#[cfg(all(feature = "macros", any(feature = "d57", feature = "wide")))]
498pub use decimal_scaled_macros::d57;
499
500#[cfg(all(feature = "macros", any(feature = "d115", feature = "wide")))]
501pub use decimal_scaled_macros::d115;
502
503#[cfg(all(feature = "macros", any(feature = "d230", feature = "wide")))]
504pub use decimal_scaled_macros::d230;
505
506#[cfg(all(feature = "macros", any(feature = "d462", feature = "x-wide")))]
507pub use decimal_scaled_macros::d462;
508
509#[cfg(all(feature = "macros", any(feature = "d616", feature = "x-wide")))]
510pub use decimal_scaled_macros::d616;
511
512#[cfg(all(feature = "macros", any(feature = "d924", feature = "xx-wide")))]
513pub use decimal_scaled_macros::d924;
514
515#[cfg(all(feature = "macros", any(feature = "d1232", feature = "xx-wide")))]
516pub use decimal_scaled_macros::d1232;
517
518// Per-scale wrappers — curated subset of pre-baked
519// `<dN>s<SCALE>!` macros that forward to the corresponding
520// proc-macro with `scale N` added. Long-tail scales remain
521// reachable via the explicit `, scale N` qualifier.
522//
523// Each alias is a tiny `macro_rules!`. We don't generate them
524// through a nested macro because `macro_rules!` doesn't support
525// directly emitting another `macro_rules!` without `$$` escapes
526// that aren't available in stable Rust; explicit per-line
527// declarations keep things debuggable and only cost ~40 lines.
528
529// D9 curated scales.
530/// `d9s0!(value)` — equivalent to `d9!(value, scale 0)`.
531#[cfg(feature = "macros")] #[macro_export]
532macro_rules! d9s0  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d9!($v, scale 0  $(, $($rest)*)?) }; }
533/// `d9s2!(value)` — equivalent to `d9!(value, scale 2)`.
534#[cfg(feature = "macros")] #[macro_export]
535macro_rules! d9s2  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d9!($v, scale 2  $(, $($rest)*)?) }; }
536/// `d9s4!(value)` — equivalent to `d9!(value, scale 4)`.
537#[cfg(feature = "macros")] #[macro_export]
538macro_rules! d9s4  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d9!($v, scale 4  $(, $($rest)*)?) }; }
539/// `d9s6!(value)` — equivalent to `d9!(value, scale 6)`.
540#[cfg(feature = "macros")] #[macro_export]
541macro_rules! d9s6  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d9!($v, scale 6  $(, $($rest)*)?) }; }
542
543// D18 curated scales.
544/// `d18s0!(value)` — equivalent to `d18!(value, scale 0)`.
545#[cfg(feature = "macros")] #[macro_export]
546macro_rules! d18s0  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 0  $(, $($rest)*)?) }; }
547/// `d18s2!(value)` — equivalent to `d18!(value, scale 2)`.
548#[cfg(feature = "macros")] #[macro_export]
549macro_rules! d18s2  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 2  $(, $($rest)*)?) }; }
550/// `d18s4!(value)` — equivalent to `d18!(value, scale 4)`.
551#[cfg(feature = "macros")] #[macro_export]
552macro_rules! d18s4  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 4  $(, $($rest)*)?) }; }
553/// `d18s6!(value)` — equivalent to `d18!(value, scale 6)`.
554#[cfg(feature = "macros")] #[macro_export]
555macro_rules! d18s6  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 6  $(, $($rest)*)?) }; }
556/// `d18s9!(value)` — equivalent to `d18!(value, scale 9)`.
557#[cfg(feature = "macros")] #[macro_export]
558macro_rules! d18s9  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 9  $(, $($rest)*)?) }; }
559/// `d18s12!(value)` — equivalent to `d18!(value, scale 12)`.
560#[cfg(feature = "macros")] #[macro_export]
561macro_rules! d18s12 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d18!($v, scale 12 $(, $($rest)*)?) }; }
562
563// D38 curated scales.
564/// `d38s0!(value)` — equivalent to `d38!(value, scale 0)`.
565#[cfg(feature = "macros")] #[macro_export]
566macro_rules! d38s0  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 0  $(, $($rest)*)?) }; }
567/// `d38s2!(value)` — equivalent to `d38!(value, scale 2)`.
568#[cfg(feature = "macros")] #[macro_export]
569macro_rules! d38s2  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 2  $(, $($rest)*)?) }; }
570/// `d38s4!(value)` — equivalent to `d38!(value, scale 4)`.
571#[cfg(feature = "macros")] #[macro_export]
572macro_rules! d38s4  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 4  $(, $($rest)*)?) }; }
573/// `d38s6!(value)` — equivalent to `d38!(value, scale 6)`.
574#[cfg(feature = "macros")] #[macro_export]
575macro_rules! d38s6  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 6  $(, $($rest)*)?) }; }
576/// `d38s8!(value)` — equivalent to `d38!(value, scale 8)`.
577#[cfg(feature = "macros")] #[macro_export]
578macro_rules! d38s8  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 8  $(, $($rest)*)?) }; }
579/// `d38s9!(value)` — equivalent to `d38!(value, scale 9)`.
580#[cfg(feature = "macros")] #[macro_export]
581macro_rules! d38s9  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 9  $(, $($rest)*)?) }; }
582/// `d38s12!(value)` — equivalent to `d38!(value, scale 12)`.
583#[cfg(feature = "macros")] #[macro_export]
584macro_rules! d38s12 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 12 $(, $($rest)*)?) }; }
585/// `d38s15!(value)` — equivalent to `d38!(value, scale 15)`.
586#[cfg(feature = "macros")] #[macro_export]
587macro_rules! d38s15 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 15 $(, $($rest)*)?) }; }
588/// `d38s18!(value)` — equivalent to `d38!(value, scale 18)`.
589#[cfg(feature = "macros")] #[macro_export]
590macro_rules! d38s18 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 18 $(, $($rest)*)?) }; }
591/// `d38s24!(value)` — equivalent to `d38!(value, scale 24)`.
592#[cfg(feature = "macros")] #[macro_export]
593macro_rules! d38s24 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 24 $(, $($rest)*)?) }; }
594/// `d38s35!(value)` — equivalent to `d38!(value, scale 35)`.
595#[cfg(feature = "macros")] #[macro_export]
596macro_rules! d38s35 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d38!($v, scale 35 $(, $($rest)*)?) }; }
597
598// D76 curated scales.
599/// `d76s0!(value)` — equivalent to `d76!(value, scale 0)`.
600#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
601macro_rules! d76s0  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 0  $(, $($rest)*)?) }; }
602/// `d76s2!(value)` — equivalent to `d76!(value, scale 2)`.
603#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
604macro_rules! d76s2  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 2  $(, $($rest)*)?) }; }
605/// `d76s6!(value)` — equivalent to `d76!(value, scale 6)`.
606#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
607macro_rules! d76s6  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 6  $(, $($rest)*)?) }; }
608/// `d76s12!(value)` — equivalent to `d76!(value, scale 12)`.
609#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
610macro_rules! d76s12 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 12 $(, $($rest)*)?) }; }
611/// `d76s18!(value)` — equivalent to `d76!(value, scale 18)`.
612#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
613macro_rules! d76s18 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 18 $(, $($rest)*)?) }; }
614/// `d76s35!(value)` — equivalent to `d76!(value, scale 35)`.
615#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
616macro_rules! d76s35 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 35 $(, $($rest)*)?) }; }
617/// `d76s50!(value)` — equivalent to `d76!(value, scale 50)`.
618#[cfg(all(feature = "macros", any(feature = "d76", feature = "wide")))] #[macro_export]
619macro_rules! d76s50 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d76!($v, scale 50 $(, $($rest)*)?) }; }
620
621// D153 curated scales.
622/// `d153s0!(value)` — equivalent to `d153!(value, scale 0)`.
623#[cfg(all(feature = "macros", any(feature = "d153", feature = "wide")))] #[macro_export]
624macro_rules! d153s0   { ($v:tt $(, $($rest:tt)*)?) => { $crate::d153!($v, scale 0   $(, $($rest)*)?) }; }
625/// `d153s35!(value)` — equivalent to `d153!(value, scale 35)`.
626#[cfg(all(feature = "macros", any(feature = "d153", feature = "wide")))] #[macro_export]
627macro_rules! d153s35  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d153!($v, scale 35  $(, $($rest)*)?) }; }
628/// `d153s75!(value)` — equivalent to `d153!(value, scale 75)`.
629#[cfg(all(feature = "macros", any(feature = "d153", feature = "wide")))] #[macro_export]
630macro_rules! d153s75  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d153!($v, scale 75  $(, $($rest)*)?) }; }
631/// `d153s150!(value)` — equivalent to `d153!(value, scale 150)`.
632#[cfg(all(feature = "macros", any(feature = "d153", feature = "wide")))] #[macro_export]
633macro_rules! d153s150 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d153!($v, scale 150 $(, $($rest)*)?) }; }
634
635// D307 curated scales.
636/// `d307s0!(value)` — equivalent to `d307!(value, scale 0)`.
637#[cfg(all(feature = "macros", any(feature = "d307", feature = "wide", feature = "x-wide")))] #[macro_export]
638macro_rules! d307s0   { ($v:tt $(, $($rest:tt)*)?) => { $crate::d307!($v, scale 0   $(, $($rest)*)?) }; }
639/// `d307s35!(value)` — equivalent to `d307!(value, scale 35)`.
640#[cfg(all(feature = "macros", any(feature = "d307", feature = "wide", feature = "x-wide")))] #[macro_export]
641macro_rules! d307s35  { ($v:tt $(, $($rest:tt)*)?) => { $crate::d307!($v, scale 35  $(, $($rest)*)?) }; }
642/// `d307s150!(value)` — equivalent to `d307!(value, scale 150)`.
643#[cfg(all(feature = "macros", any(feature = "d307", feature = "wide", feature = "x-wide")))] #[macro_export]
644macro_rules! d307s150 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d307!($v, scale 150 $(, $($rest)*)?) }; }
645/// `d307s300!(value)` — equivalent to `d307!(value, scale 300)`.
646#[cfg(all(feature = "macros", any(feature = "d307", feature = "wide", feature = "x-wide")))] #[macro_export]
647macro_rules! d307s300 { ($v:tt $(, $($rest:tt)*)?) => { $crate::d307!($v, scale 300 $(, $($rest)*)?) }; }