Rutba

Blog · Money

Every rounding goes to the customer

Software is full of places where the arithmetic is not exact. Each one is a decision about who absorbs the fraction, and we made that decision once, in advance, for all of them.

· 3 min read

A usage meter with the needle sitting below the reading, and an arrow labelled “rounded to you”.
Where the needle stops when the arithmetic is not exact.

A bill is arithmetic, and arithmetic on money is rarely exact. Thirty-one days into a thirty-day rate. A discount of 22% on $18.00. Fifty thousand messages billed in blocks of a thousand when you sent fifty thousand and one. Every one of those has a fraction, and somebody absorbs it.

Most billing systems decide that case by case, usually by whichever way the language rounds by default. Ours decided once: the fraction goes to the customer, every time, without needing a reason.

Why it is a rule and not a judgement

The alternative is a policy that says "round fairly", which in practice means each developer decides at the moment they write the line — and their decision is invisible, because a rounding is one character. Math.floor and Math.ceil differ by a penny and by an entire posture, and code review does not reliably catch the difference.

A fixed direction is checkable. Anyone can read a money function and see whether it rounds the way the rule says, without knowing what the number means.

Where it actually applies

SituationWhat is not exactWhich way we go
A discount on a line22% of $18.00 is $3.96Round the discount up
A refund for unused days25 of 30 days of $54.00Round the refund up
Days in a period30.5 days in the monthRound the period down, so a day is worth more
A part day10 minutes remainingCount it as a whole unused day
Metered blocks50,001 messages in blocks of 1,000Charge the whole blocks used, floor the remainder

Note that the direction is not always "up". It is always *toward the customer*, which for a discount means up and for the divisor in a daily rate means down. Stating it as "always round up" would get half of these wrong.

And before any of that: no floats

Every amount in the system is an integer in the currency’s minor unit. $18.00 is 1800. Nothing is ever a floating-point number, at any point, in any layer — not in the database, not in the API, not on the way to a page.

0.1 + 0.2 === 0.30000000000000004

That is not a curiosity. It is a line on an invoice
that does not add up, and a customer who is right.

A currency’s minor unit is not always two decimal places either — the yen has none, the dinar has three — so the exponent comes from the currency rather than from an assumption that everything is cents.

The corollary: never show a number we cannot stand behind

The same rule has a second half that costs us more than the pennies do: if we cannot compute a figure honestly, we do not display one.

Our usage page shows last month’s metered charge and, for the month in progress, a statement that it is being counted — not a running estimate. A part-month priced now and re-priced at close would bill its first days twice, so the honest thing to say is that the meter is running.

None of this is expensive. Across a year of invoices these decisions add up to a rounding error, which is the point: it costs almost nothing, and it means nobody ever has to check our arithmetic to know it was not tilted.

Every price, in public

Per unit, per month, with the edges named. Including the ones that are awkward to explain.

See the pricing

Read next