Blog
How Boring Expenses stores money (and why it's an integer)
Here’s a classic way to lose a cent. Ask almost any programming language what
0.1 + 0.2 equals and it will tell you 0.30000000000000004. That’s not a bug in the
language — it’s how binary floating-point numbers work. They can’t represent most
decimal fractions exactly, so tiny errors creep in. Fine for physics; not fine for money.
For a finance app, “close enough” is the wrong answer. So Boring Expenses never stores an amount as a decimal at all.
Store the smallest unit as a whole number
The trick money software has used forever is to work in the smallest unit and keep it as
an integer. Instead of $4.20, you store 420 — the number of cents. Integers are
exact. Add, subtract, and total them all day and you never accumulate rounding dust.
Boring Expenses takes this a step further and stores amounts in very fine sub-units
(think “100 million per whole unit”), which leaves plenty of headroom for currencies and
future needs while keeping everything as a plain Int. Conversion to and from what you
actually see on screen happens at the very edges — when you type an amount in, and when
we format one out.
// Illustration, not the exact code:
let display = Decimal(amountInSubunits) / Decimal(subunitsPerUnit)
let text = display.formatted(.currency(code: userCurrency))
Why this matters for you
- Totals are exact. Your monthly sum is the real sum, not a number that drifts by a cent as the list grows.
- Sync stays consistent. An integer means the same thing on your iPhone, your Watch, and your iPad. There’s no ambiguity to reconcile.
- Exports are trustworthy. The CSV you export matches the app to the penny.
It’s a small, unglamorous decision — exactly the kind we like. You’ll never see it, and that’s the point: the numbers just add up.