Arbitrary-Precision Decimal Arithmetic in JavaScript
Binary floating-point values cannot represent many decimal fractions exactly. This is why expressions such as 0.1 + 0.2 do not produce an exact decimal 0.3. Financial, accounting, tax, pricing, and measurement code often needs decimal arithmetic with explicit precision and rounding.
Decimal representation
A decimal implementation normally stores:
value = unscaledInteger × 10^(-scale)
For example, 123.45 can be represented as an unscaled integer 12345 with scale 2. Modern implementations may use BigInt, arrays of base-10 or base-1eN digits, or another arbitrary-precision integer representation.
Parse strings, not floating-point values
Construct decimal values from strings whenever exact decimal input matters:
const price = new Decimal("19.99");
const quantity = new Decimal("3");
const total = price.times(quantity);
Passing an already rounded JavaScript number cannot recover precision that was previously lost.
Core operations
A useful decimal API should support:
- addition and subtraction;
- multiplication and division;
- comparison and equality;
- scale changes and quantization;
- absolute value and negation;
- conversion to plain or scientific notation;
- configurable precision and rounding modes.
Division needs an explicit result scale or precision because many decimal divisions do not terminate:
const result = new Decimal("1")
.div(new Decimal("3"))
.toDecimalPlaces(2, Decimal.ROUND_HALF_UP);
// 0.33
Rounding
Common modes include toward zero, away from zero, floor, ceiling, half-up, half-down, and half-even. The correct mode is a business rule, not merely a technical preference. Banker's rounding (half-even) reduces aggregate bias in some workloads, while tax regulations may require another method.
Always define where rounding occurs. Rounding each line item and then summing can differ from summing exact values and rounding only the final total.
Comparison
Do not compare formatted strings or convert back to number:
amount.comparedTo(limit) < 0
amount.equals(other)
Decide whether equality treats 1.0 and 1.00 as numerically equal or also considers scale. Both semantics are useful in different domains.
Money model
For a single currency with a fixed minor unit, storing integer cents is often simpler:
const unitPriceCents = 1999n;
const quantity = 3n;
const totalCents = unitPriceCents * quantity;
Use arbitrary-precision decimals when rates, percentages, multiple scales, currency conversion, or regulated rounding rules make integer minor units insufficient.
Serialization
Serialize exact decimal values as strings unless both sides have a documented decimal-capable format:
{
"amount": "1234567890.123456"
}
JSON numbers are usually parsed into binary floating-point values and may lose precision.
Production checklist
Use a maintained decimal library rather than a copied historical implementation. Pin the version, test negative values and zero, define maximum precision, reject malformed or unbounded inputs, document rounding rules, and verify results against authoritative examples. Keep formatting separate from arithmetic so locale-specific commas, currency symbols, and display rounding do not affect stored values.