Money for the financial-domain alias of the same template.ddn.num.fixed_decimal
ddn.num.fixed_decimal — bounded fixed-point decimal values.
FixedDecimal!(Storage, Scale) represents exact decimal values as coefficient × 10⁻ˢᶜᵃˡᵉ, where the developer picks the underlying integer storage and the decimal-digit precision is derived from it:
byte— 2 digits,short— 4,int— 9,long— 18.std.int128.Int128— 38 digits (thecent-shaped class).
Scale is the number of fraction digits: -1 (the default) selects the runtime-scale form (a stored ubyte in 0..precision); any other value fixes the scale at compile time and stores no scale field. Values are exact — every conversion is checked and inexact operations throw rather than round (rounding arrives with the arithmetic layer, under an explicit policy).This is fixed-point, not IEEE 754 decimal floating point: no per-value exponents, no NaN/Inf, no rounding modes. Type.DECIMAL in ddn.var is reserved for that different family.
Money is a template alias of FixedDecimal for financial spelling — the same types, no currency identity and no default scale.
The type is standalone: it depends on Phobos only (never ddn.var, formats or encodings), and all operations are CTFE-safe — compile-time constant decimals work from day one.
Design record: doc/DDN-FIXED-DECIMAL.md.
Example:
import ddn.num.fixed_decimal;
auto price = FixedDecimal!long.fromString("19.99");
assert(price.precision == 18);
assert(price.toString() == "19.99");
auto rate = Money!(long, 4).fromString("0.0750");
assert(rate.scale == 4);
enum oneFive = FixedDecimal!long.fromString("1.5"); // CTFE works
static assert(oneFive.toString() == "1.5");Types 4
Thrown for rejected fixed-decimal operations: malformed parsing, precision overflow and inexact conversions.
this(string msg)Params: msg = description of the rejected operation.Bounded fixed-point decimal: value = coefficient × 10⁻ˢᶜᵃˡᵉ.
Parameters
Storage | underlying integer storage: byte, short, int, long or std.int128.Int128. The decimal-digit precision is derived from it (precisionOf!Storage). |
Scale | fraction digit count; -1 (default) selects the runtime-scale form, 0..precisionOf!Storage fixes the scale at compile time. |
See Also
int precisionDerived decimal-digit precision of this type.int scaleTemplateCompile-time scale; `-1` for the runtime-scale form.private I _coefficientubyte scale() @property const @safe pure nothrow @nogcThe scale (fraction digit count): the template constant for fixed-scale forms, the stored byte for runtime-scale ones.FixedDecimal fromUnscaled(long coefficient, ubyte scale) static @safe pureBuilds a value from unscaled integer parts.auto atScale(int S2)() if (S2 >= 0 && S2 <= precisionOf!I) const @safe pureConverts to a fixed-scale type (checked).FixedDecimal!(I, - 1) _alignedSum(bool sub)(
FixedDecimal!(I, - 1) a, FixedDecimal!(I, - 1) b) static @safe pureAdds or subtracts two runtime-form operands of this storage class, aligning to the larger scale (checked upscaling; the aligned sum must fit the precision or the operation throws).FixedDecimal _fromInteger(long n) static @safe pureThe same integer as a value of this exact instantiation.FixedDecimal opBinary(string op)(const FixedDecimal rhs) if (op == "+" || op == "-") const @safe pure`+`/`-` between values of the same instantiation: the result is that same instantiation (fixed-scale operands share their scale, so alignment is identity).auto opBinary(string op, int S2)(const FixedDecimal!(I, S2) rhs) if ((op == "+" || op == "-") && S2 != Scale) const @safe pure`+`/`-` between fixed-scale forms of different compile-time scale (same storage): operands convert to the runtime form; the result is the runtime form at the aligned scale.auto opBinary(string op, J2, int S2)(const FixedDecimal!(J2, S2) rhs) if ((op == "+" || op == "-") && !is(J2 == I)) const @safe pure`+`/`-` across storage classes: both operands widen to the `Int128` runtime form (exact); the result is the wide runtime form.auto opBinary(string op)(const long rhs) if (op == "+" || op == "-") const @safe pure`+`/`-` with a signed integer operand (exact conversion or throw).auto opBinaryRight(string op)(const long lhs) if (op == "+" || op == "-") const @safe pureDitto (integer on the left).FixedDecimal opUnary(string op)() if (op == "-") const @safe pure nothrow @nogcNegation (sign flip via the magnitude path, so negating the largest negative coefficient cannot overflow).FixedDecimal!(I, - 1) _product(FixedDecimal!(I, - 1) a,
FixedDecimal!(I, - 1) b) static @safe pureExact product of two runtime-form operands: result scale `s1 + s2`, coefficient checked against the precision. Never rounds (design record D2/D3).FixedDecimal!(I, - 1) _quotient(FixedDecimal!(I, - 1) a,
FixedDecimal!(I, - 1) b, ubyte rs) static @safe pureQuotient of two runtime-form operands at an explicit result scale, rounded half-even (the single rounding rule, D3/D4).auto opBinary(string op)(const FixedDecimal rhs) if (op == "*") const @safe pure`*` between values of the same instantiation. Fixed-scale operands yield the fixed-scale type of the summed scale (`S + S`, compile-time checked); runtime-scale operands yield the runtime form.auto opBinary(string op, int S2)(const FixedDecimal!(I, S2) rhs) if (op == "*" && S2 != Scale) const @safe pure`*` between different scale forms of the same storage: fixed × fixed yields `FixedDecimal!(I, S1 + S2)` (compile-time checked); any runtime-scale operand yields the runtime form.auto opBinary(string op, J2, int S2)(const FixedDecimal!(J2, S2) rhs) if (op == "*" && !is(J2 == I)) const @safe pure`*` across storage classes: both operands widen to the `Int128` runtime form; the result is the wide runtime form.auto opBinary(string op)(const long rhs) if (op == "*") const @safe pure`*` with a signed integer operand (exact conversion or throw).auto opBinaryRight(string op)(const long lhs) if (op == "*") const @safe pureDitto (integer on the left).auto opBinary(string op)(const FixedDecimal rhs) if (op == "/") const @safe pureDivision: result scale is `max(s1, s2)` rounded half-even (convenience form, D4). Precision-critical callers use divideToScale. The result is the runtime-scale form.auto opBinary(string op, int S2)(const FixedDecimal!(I, S2) rhs) if (op == "/" && S2 != Scale) const @safe pureDitto, different scale forms of the same storage.auto opBinary(string op, J2, int S2)(const FixedDecimal!(J2, S2) rhs) if (op == "/" && !is(J2 == I)) const @safe pureDitto, across storage classes (widen both; wide runtime result).auto opBinary(string op)(const long rhs) if (op == "/") const @safe pure`/` by a signed integer (exact conversion or throw).auto opBinaryRight(string op)(const long lhs) if (op == "/") const @safe pureDitto (integer divided by a decimal).auto divideToScale(int S2)(const FixedDecimal!(I, S2) rhs,
ubyte resultScale) const @safe pureDivision with an explicit result scale, rounded half-even.bool opEquals(ref const typeof(this) rhs) const @safe pureStructural-value equality (numeric, across scales and forms).bool opEquals(FD)(auto ref const FD rhs) if (_isFD!FD && !is(FD == typeof(this))) const @safe pureMixed-type equality: any `FixedDecimal` instantiation compares by value.int opCmp(ref const typeof(this) rhs) const @safe pureNumeric ordering (total, across scales and forms).int opCmp(FD)(auto ref const FD rhs) if (_isFD!FD && !is(FD == typeof(this))) const @safe pureMixed-type ordering: any `FixedDecimal` instantiation.size_t toHash() const @safe pureHash consistent with value equality: equal values hash equal across scales, capacities and scale forms (canonical form, trailing fraction zeros stripped).ubyte[16] _magOf(Int128 v) static @safe pure nothrow @nogcBig-endian magnitude bytes of an `Int128` (sign stripped).string _canonical() const @safe pureCanonical digit form: sign + integer digits + '.' + fraction (zeros trimmed).int _valueCmp(FD)(auto ref const FD rhs) const @safe pureValue comparison core shared by every storage, scale and form.int _canonicalBodyCmp(string a, string b) static @safe pureCompares two nonnegative canonical bodies.Financial-domain alias of the whole FixedDecimal template: both signatures, identical semantics — no currency identity, no default scale of 2, not a distinct nominal type.
Convenience alias for the runtime-scale Int128-backed (38-digit) specialization.
Functions 17
ubyte _magMul10(ref ubyte[16] mag) @safe pure nothrow @nogcMultiplies a big-endian magnitude by 10 in place; returns the carry.ubyte _magDiv10(ref ubyte[16] mag) @safe pure nothrow @nogcDivides a big-endian magnitude by 10 in place; returns the remainder.bool _magIsZero(in ubyte[16] mag) @safe pure nothrow @nogcTrue when a big-endian magnitude is zero.int _magCmp(in ubyte[16] a, in ubyte[16] b) @safe pure nothrow @nogcCompares big-endian magnitudes: `-1`, `0` or `1`.ubyte _magAddInto(ref ubyte[16] mag, in ubyte[16] src) @safe pure nothrow @nogcAdds `src` into a big-endian magnitude; returns the carry-out byte.void _magSubFrom(ref ubyte[16] mag, in ubyte[16] src) @safe pure nothrow @nogcSubtracts `src` from a big-endian magnitude in place. The caller guarantees `mag >= src` (compare with `magCmp` first).void _magAddSub(int PrecisionDigits, bool sub)(
ref ubyte[16] ma, ref bool nega, ubyte[16] mb,
const bool negb) @safe pureAdds or subtracts aligned signed magnitudes (sign applied to `b` is flipped for subtraction). On return `ma`/`nega` hold the result.bool _mag32IsZero(in ubyte[32] mag) @safe pure nothrow @nogcTrue when a 32-byte big-endian magnitude is zero.int _mag32Cmp(in ubyte[32] a, in ubyte[32] b) @safe pure nothrow @nogcCompares 32-byte big-endian magnitudes: `-1`, `0` or `1`.void _mag32Shl1(ref ubyte[32] mag) @safe pure nothrow @nogcShifts a 32-byte big-endian magnitude left one bit (top bit lost).void _mag32SubFrom(ref ubyte[32] mag, in ubyte[32] src) @safe pure nothrow @nogcSubtracts `src` from a 32-byte magnitude in place (`mag >= src`).void _magMulWide(ref ubyte[32] dst, in ubyte[16] a,
in ubyte[16] b) @safe pure nothrow @nogcMultiplies two 16-byte magnitudes into a 32-byte product (schoolbook, base-256; cannot overflow: product < 2^256).void _mag32DivMod(in ubyte[32] num, in ubyte[32] den,
out ubyte[32] q, out ubyte[32] r) @safe pure nothrow @nogcFull 32-byte division with remainder (bit-by-bit long division, MSB first). The divisor must be nonzero.char[] _magDigits(ubyte[16] mag) @safe pureDecimal digits of a magnitude, most-significant first (`0` → "0").void _magMul10Add(ref ubyte[16] mag, int digit) @safe pureMultiplies a magnitude by 10 and adds one digit (0..9); overflow throws.void _magMul10AddChecked(int PrecisionDigits)(
ref ubyte[16] mag, int digit) @safe pureMultiplies a magnitude by 10, adds one digit and enforces the decimal precision (the byte-carry checks in `magMul10Add` alone only detect overflow of the 16-byte array, not of a narrower digit prec...string repeatStr(char c, size_t n) @safe pureRepeats `'0'` `n` times (small helper, CTFE-safe).Templates 3
Derived full decimal-digit precision of a storage type: the largest d with 10^d − 1 representable as a positive magnitude.
Parameters
I | one of the supported signed storage types. |
Returns
long → 18, Int128 → 38).
Compile-time 10^precision − 1 as a big-endian magnitude.
True when T is any FixedDecimal instantiation.