Struct DecimalAmount
Fixed-point decimal amount with base 10, stored as a whole part, a fractional part, and a precision (number of fractional digits). Backed by long for the whole part and ulong for the fractional part so values up to roughly 10^18 are representable without floating-point error.
public readonly record struct DecimalAmount : IFixedPointAmount<DecimalAmount>, IFixedPointAmount, IComparable<DecimalAmount>, IAsCardinal<long>, IAmount<(long, ulong)>, IAmount, IEquatable<IAmount<(long, ulong)>>, IComparable<IAmount<(long, ulong)>>, IAdditionOperators<DecimalAmount, DecimalAmount, DecimalAmount>, ISubtractionOperators<DecimalAmount, DecimalAmount, DecimalAmount>, IMultiplyOperators<DecimalAmount, DecimalAmount, DecimalAmount>, IDivisionOperators<DecimalAmount, DecimalAmount, DecimalAmount>, IEquatable<DecimalAmount>
- Implements
- Inherited Members
Examples
var a = new DecimalAmount(1, 23, 2); // 1.23
var b = new DecimalAmount(0, 50, 2); // 0.50
var sum = a + b; // 1.73
Console.WriteLine(sum); // "1.73"
Remarks
Addition and subtraction use checked semantics and throw
OverflowException on overflow. Subtraction is allowed to go
negative; the caller decides whether to throw on negative results.
Equality and ordering both include the precision: an amount at a
different N is a different value. The invariant cannot be enforced
statically (C# lacks dependent types), so it is enforced at runtime -- an
amount at precision 2 and one at precision 4 are, in effect, different
types.
They report a mismatch differently, and deliberately so, following the
BCL's own split for a type mismatch: equality returns false (as
Equals(object) does when handed another type), while
ordering throws PrecisionMismatchException (as
CompareTo(object) throws
ArgumentException). Equality must stay total because
dictionaries, Contains, Distinct and the generated equality of
any record holding an amount all call it where a throw cannot be caught.
Arithmetic keeps throwing: unlike a comparison, 5.00 + 5.0000 has no
answer until someone picks a precision.
Multiplication and division are delegated to FixedPointArithmetic: the product is expressed at the summed precision, the quotient at the dividend's precision.
Constructors
DecimalAmount(long, ulong, int)
Fixed-point decimal amount with base 10, stored as a whole part, a fractional part, and a precision (number of fractional digits). Backed by long for the whole part and ulong for the fractional part so values up to roughly 10^18 are representable without floating-point error.
public DecimalAmount(long Whole, ulong Fraction, int N)
Parameters
Examples
var a = new DecimalAmount(1, 23, 2); // 1.23
var b = new DecimalAmount(0, 50, 2); // 0.50
var sum = a + b; // 1.73
Console.WriteLine(sum); // "1.73"
Remarks
Addition and subtraction use checked semantics and throw
OverflowException on overflow. Subtraction is allowed to go
negative; the caller decides whether to throw on negative results.
Equality and ordering both include the precision: an amount at a
different N is a different value. The invariant cannot be enforced
statically (C# lacks dependent types), so it is enforced at runtime -- an
amount at precision 2 and one at precision 4 are, in effect, different
types.
They report a mismatch differently, and deliberately so, following the
BCL's own split for a type mismatch: equality returns false (as
Equals(object) does when handed another type), while
ordering throws PrecisionMismatchException (as
CompareTo(object) throws
ArgumentException). Equality must stay total because
dictionaries, Contains, Distinct and the generated equality of
any record holding an amount all call it where a throw cannot be caught.
Arithmetic keeps throwing: unlike a comparison, 5.00 + 5.0000 has no
answer until someone picks a precision.
Multiplication and division are delegated to FixedPointArithmetic: the product is expressed at the summed precision, the quotient at the dividend's precision.
Fields
DefaultPrecision
Default precision (fractional digits) for newly constructed amounts when no precision is supplied. Matches conventional currency representation.
public const int DefaultPrecision = 2
Field Value
MaxPrecision
Largest precision a DecimalAmount can express: 10^18 fits in a long, 10^19 does not.
public const int MaxPrecision = 18
Field Value
Radix
Radix of the fractional representation.
public const int Radix = 10
Field Value
Properties
Base
Radix of the fractional representation (e.g. 10 or 2). Always >= 2.
public int Base { get; }
Property Value
BaseValue
The type's fixed base, usable in generic code without an instance. Mirrors the instance Base.
public static int BaseValue { get; }
Property Value
Fraction
The fractional part, a magnitude in [0, Base^Precision).
public ulong Fraction { get; init; }
Property Value
IsZero
true when this amount equals the additive identity.
public bool IsZero { get; }
Property Value
N
public int N { get; init; }
Property Value
Precision
Number of fractional digits carried by this amount.
public int Precision { get; }
Property Value
Whole
The whole (integral) part.
public long Whole { get; init; }
Property Value
Zero
A zero amount at DefaultPrecision precision.
public static DecimalAmount Zero { get; }
Property Value
Methods
CompareTo(DecimalAmount)
Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
public int CompareTo(DecimalAmount other)
Parameters
otherDecimalAmountAn object to compare with this instance.
Returns
- int
A value that indicates the relative order of the objects being compared. The return value has these meanings:
Value Meaning Less than zero This instance precedes otherin the sort order.Zero This instance occurs in the same position in the sort order as other.Greater than zero This instance follows otherin the sort order.
Exceptions
- PrecisionMismatchException
Precisions differ.
CompareTo(IAmount)
Compare with another amount. Implementations reject differing scales (base/precision) at runtime via PrecisionMismatchException.
public int CompareTo(IAmount other)
Parameters
otherIAmountAmount to compare against.
Returns
Create(long, ulong, int)
Construct an instance from decomposed parts at the given precision.
public static DecimalAmount Create(long whole, ulong fraction, int precision)
Parameters
wholelongWhole part.
fractionulongFractional magnitude in
[0, Base^precision).precisionintNumber of fractional digits.
Returns
Equals(DecimalAmount)
Indicates whether the current object is equal to another object of the same type.
public bool Equals(DecimalAmount other)
Parameters
otherDecimalAmountAn object to compare with this object.
Returns
Remarks
Precision is part of identity: an amount at a different N is a different
value, never an equal one. It returns false rather than throwing, unlike
CompareTo(DecimalAmount) -- that asymmetry is deliberate and mirrors the BCL,
where a type mismatch makes Equals(object) return false
but CompareTo(object) throw. Equality is called
implicitly by dictionaries, Contains, Distinct and by the generated
equality of any record holding an amount, where a throw cannot be caught.
From(decimal, int)
Construct a DecimalAmount from a decimal. This is the exact constructor — no double round-trip.
public static DecimalAmount From(decimal value, int n)
Parameters
Returns
From(double, int)
Construct a DecimalAmount from a double. Prefer From(decimal, int) for exact values; this overload exists for call sites that already hold a double.
public static DecimalAmount From(double value, int n)
Parameters
Returns
FromCardinal(long)
Recover whole and fractional parts from a cardinal value at this amount's precision.
public (long Whole, ulong Fraction) FromCardinal(long cardinal)
Parameters
cardinallongCardinal value to decompose.
Returns
FromCardinal(long, int)
Recover whole and fractional parts from a cardinal value at an explicit precision.
public static (long Whole, ulong Fraction) FromCardinal(long cardinal, int n)
Parameters
Returns
GetHashCode()
Returns the hash code for this instance.
public override int GetHashCode()
Returns
- int
A 32-bit signed integer that is the hash code for this instance.
GetValue()
public (long Whole, ulong Fraction) GetValue()
Returns
ToCardinal()
Compute the cardinal (single-integer) representation at this amount's precision.
public long ToCardinal()
Returns
ToCardinal(int)
Compute the cardinal (single-integer) representation at an explicit
precision. Rescales when n differs from
N, rounding half away from zero.
public long ToCardinal(int n)
Parameters
nintTarget precision (fractional digits).
Returns
ToDecimal()
Convert to decimal exactly.
public decimal ToDecimal()
Returns
ToString()
Format the amount as a decimal string.
public override string ToString()
Returns
Operators
operator +(DecimalAmount, DecimalAmount)
Add two amounts. Throws OverflowException on overflow.
public static DecimalAmount operator +(DecimalAmount left, DecimalAmount right)
Parameters
leftDecimalAmountrightDecimalAmount
Returns
operator /(DecimalAmount, DecimalAmount)
Divide two amounts. The result carries the dividend's precision, rounded half away from zero. Throws DivideByZeroException when the divisor is zero.
public static DecimalAmount operator /(DecimalAmount left, DecimalAmount right)
Parameters
leftDecimalAmountrightDecimalAmount
Returns
operator *(DecimalAmount, DecimalAmount)
Multiply two amounts. The result carries the summed precision of the operands (exact in base 10). Throws OverflowException on overflow.
public static DecimalAmount operator *(DecimalAmount left, DecimalAmount right)
Parameters
leftDecimalAmountrightDecimalAmount
Returns
operator -(DecimalAmount, DecimalAmount)
Subtract two amounts. Throws OverflowException on overflow.
public static DecimalAmount operator -(DecimalAmount left, DecimalAmount right)
Parameters
leftDecimalAmountrightDecimalAmount