DEV Community

Cover image for Global Precision & Financial Calculations
NorfolkD
NorfolkD

Posted on

Global Precision & Financial Calculations

Global Precision and how it could impact the financial calculations

When you're building anything that handles money, someone eventually asks:
"how many decimal places should we use?" The tempting answer is to pick a
number — 2, or maybe 5 to be safe — set it globally, and move on. It feels
like a reasonable call. It's not.

I've debugged precision bugs that traced back exactly to this decision.
Once you understand why a single global precision fails, you can't unsee it.


The core problem is that different parts of a financial system have genuinely
different precision requirements, and treating them the same introduces errors
at the boundaries.

Billing calculations that appear on invoices need 2 decimal places. That's
cent precision. That's what customers see, what they're charged, what appears
on the contract. Showing 4 decimal places here is wrong for a different reason —
you're creating a number that can't be represented in any real currency.

Analytics calculations — revenue attribution, cohort aggregations, lifetime
value — need more precision. When you're summing thousands of transactions
before producing a final figure, rounding to 2 places at each step accumulates
error that compounds in ways that matter at scale.

Internal calculations — intermediate values mid-computation — should use the
highest precision you can reasonably sustain, precisely because they feed into
further operations. Rounding early and then doing arithmetic on the rounded
value is one of the most common sources of financial calculation bugs I've
encountered.

If you apply a global setting, you're either truncating precision your
analytics pipeline actually needs, or you're putting 4-decimal numbers on
customer invoices. Neither is acceptable.


The model that works is precision as an explicit input to each domain calculator,
with sensible per-domain defaults:

type PrecisionConfig = {
  internalCalcPlaces: number;
  displayPlaces: number;
  storagePlaces: number;
};

const BILLING_PRECISION: PrecisionConfig = {
  internalCalcPlaces: 5,
  displayPlaces: 2,
  storagePlaces: 2
};
Enter fullscreen mode Exit fullscreen mode

The engine computes everything at internal precision. It rounds to display
or storage precision only when producing output — at the domain boundary,
not in the middle of a calculation chain.

This brings us to what I think is the subtlest and most consequential mistake
in financial arithmetic: rounding order.

Consider a line item. Unit price $10.333..., quantity 3, 10% discount.

Round early: you get $10.33 × 3 = $30.99, apply discount, round to $27.89.

Round late — maintain full precision through the chain: $10.333... × 3 × 0.9 = $27.899...,
round only at output: $27.90.

One cent difference on one line item. Across a large pricing table with currency
conversion also in the chain, accumulated rounding error is real, customer-visible,
and extremely difficult to debug once it's in production — because the bug is
in the order of operations, not in any individual calculation.

Round at output boundaries. Never in the middle of a computation chain.

Currency conversion adds another layer to this. Rates are typically 4–6 decimal
places. If you convert and immediately round to 2 decimal places before doing
further arithmetic, you've thrown away precision you needed. Convert at full
internal precision, display-round last.

And never convert and re-convert. Round-trip currency conversion
(USD → EUR → USD) with intermediate rounding will not give you back the
original number. If any part of your system displays a converted value and
then uses that displayed value in further calculation, you have a latent
bug waiting for the right combination of exchange rate and amount to surface it.


The practical steps are straightforward: define precision per domain, make
it an explicit parameter rather than global configuration, add a lint rule
that forbids raw Number arithmetic in your calculation package, and treat
output boundaries as the one and only place where rounding is allowed.

It sounds like ceremony until you've spent a day debugging a $0.01 discrepancy
on a $50,000 contract that a customer has already signed and is asking why
the numbers don't match.


Precision bugs that involve accumulated rounding are the hardest to reproduce
consistently — they depend on exact input combinations and operation order.
Has anyone built regression suites specifically for these? Property-based
testing feels like the right tool but I'm curious what others have actually shipped.

Top comments (0)