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 (the cent-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

classFixedDecimalException : Exception

Thrown for rejected fixed-decimal operations: malformed parsing, precision overflow and inexact conversions.

Constructors
this(string msg)Params: msg = description of the rejected operation.
structFixedDecimal(I, int Scale = - 1) if (is(I == byte) || is(I == short) || is(I == int) || is(I == long) || is(I == Int128))

Bounded fixed-point decimal: value = coefficient × 10⁻ˢᶜᵃˡᵉ.

Parameters

Storageunderlying integer storage: byte, short, int, long or std.int128.Int128. The decimal-digit precision is derived from it (precisionOf!Storage).
Scalefraction digit count; -1 (default) selects the runtime-scale form, 0..precisionOf!Storage fixes the scale at compile time.

See Also

Money for the financial-domain alias of the same template.
Fields
int precisionDerived decimal-digit precision of this type.
int scaleTemplateCompile-time scale; `-1` for the runtime-scale form.
private I _coefficient
Methods
ubyte 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.
FixedDecimal fromString(scope const(char)[] text) static @safe pureParses an exact decimal literal.
string toString() const @safe pureFormats the value in exact decimal notation.
auto atScale(int S2)() if (S2 >= 0 && S2 <= precisionOf!I) const @safe pureConverts to a fixed-scale type (checked).
auto asRuntime() const @safe pure nothrow @nogcThe same value as a runtime-scale instance.
private 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).
private 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 abs() const @safe pure nothrow @nogcThe absolute value (same scale/form).
private 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).
private 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).
private void _checkScale(ubyte s) static @safe pure
private void _setScale(ref FixedDecimal v, ubyte s) static @safe pure nothrow
private bool _negative() const @safe pure nothrow @nogc
private ubyte[16] _magnitude() const @safe pure nothrow @nogc
private ubyte[16] _magOf(Int128 v) static @safe pure nothrow @nogcBig-endian magnitude bytes of an `Int128` (sign stripped).
private I _toStorage(bool negative, in ubyte[16] mag) static @safe pure nothrow @nogc
private string _canonical() const @safe pureCanonical digit form: sign + integer digits + '.' + fraction (zeros trimmed).
private int _valueCmp(FD)(auto ref const FD rhs) const @safe pureValue comparison core shared by every storage, scale and form.
private int _canonicalBodyCmp(string a, string b) static @safe pureCompares two nonnegative canonical bodies.
private string _intPartOf(string c) static @safe pure nothrow @nogc
private string _fracPartOf(string c) static @safe pure nothrow @nogc

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

private fnubyte _magMul10(ref ubyte[16] mag) @safe pure nothrow @nogcMultiplies a big-endian magnitude by 10 in place; returns the carry.
private fnubyte _magDiv10(ref ubyte[16] mag) @safe pure nothrow @nogcDivides a big-endian magnitude by 10 in place; returns the remainder.
private fnbool _magIsZero(in ubyte[16] mag) @safe pure nothrow @nogcTrue when a big-endian magnitude is zero.
private fnint _magCmp(in ubyte[16] a, in ubyte[16] b) @safe pure nothrow @nogcCompares big-endian magnitudes: `-1`, `0` or `1`.
private fnubyte _magAddInto(ref ubyte[16] mag, in ubyte[16] src) @safe pure nothrow @nogcAdds `src` into a big-endian magnitude; returns the carry-out byte.
private fnvoid _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).
private fnvoid _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.
private fnbool _mag32IsZero(in ubyte[32] mag) @safe pure nothrow @nogcTrue when a 32-byte big-endian magnitude is zero.
private fnint _mag32Cmp(in ubyte[32] a, in ubyte[32] b) @safe pure nothrow @nogcCompares 32-byte big-endian magnitudes: `-1`, `0` or `1`.
private fnvoid _mag32Shl1(ref ubyte[32] mag) @safe pure nothrow @nogcShifts a 32-byte big-endian magnitude left one bit (top bit lost).
private fnvoid _mag32SubFrom(ref ubyte[32] mag, in ubyte[32] src) @safe pure nothrow @nogcSubtracts `src` from a 32-byte magnitude in place (`mag >= src`).
private fnvoid _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).
private fnvoid _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.
private fnchar[] _magDigits(ubyte[16] mag) @safe pureDecimal digits of a magnitude, most-significant first (`0` → "0").
private fnvoid _magMul10Add(ref ubyte[16] mag, int digit) @safe pureMultiplies a magnitude by 10 and adds one digit (0..9); overflow throws.
private fnvoid _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...
private fnstring repeatStr(char c, size_t n) @safe pureRepeats `'0'` `n` times (small helper, CTFE-safe).

Templates 3

tmplprecisionOf(I)

Derived full decimal-digit precision of a storage type: the largest d with 10^d − 1 representable as a positive magnitude.

Parameters

Ione of the supported signed storage types.

Returns

The precision in decimal digits (byte → 2, short → 4, int → 9,

long → 18, Int128 → 38).

Functions
Int128 _magnitudeMaxOf()
tmpl_precisionMax(int precision)

Compile-time 10^precision − 1 as a big-endian magnitude.

tmpl_isFD(T)

True when T is any FixedDecimal instantiation.