Why Money Should Never Be a Float: Integer Cents in Ahorreo

Open a browser console and type 0.1 + 0.2. The answer is 0.30000000000000004. Every developer has seen this at some point, laughed, and moved on. In most software it’s harmless. In a personal finance app it’s a slow-acting poison.

When we started building Ahorreo, the very first architectural rule we wrote down wasn’t about the UI or the storage layer. It was this: money is an integer number of minor units, always. Here’s why, and what it takes to live with that rule.

The Problem With Binary Fractions

JavaScript numbers are IEEE 754 double-precision floats. They’re binary, and most decimal fractions — 0.1 among them — have no exact binary representation, in the same way that one third has no exact decimal one. Every value is rounded to the nearest representable number, and those tiny errors accumulate as you add, subtract and multiply.

For a single sum, the error is invisible once you format to two decimals. But a finance app doesn’t do a single sum. It adds thousands of transactions to get a balance, subtracts categories from totals, scales amounts by ownership percentages, and compares its result with the balance column in your bank statement. Sooner or later, a total comes out one cent off.

One cent is not a rounding detail

When a user sees their computed balance differ from their bank's by €0.01, they don't think "floating point". They think "this app is wrong", and every other number on the screen loses its credibility at once. Trust in a finance tool is binary too.

The Rule: Integers Inside a Money Type

In Ahorreo, every amount is stored as an integer number of the currency’s smallest unit — cents for euros — inside a small value object:

type Money = {
  amountMinor: number; // 123456 means €1,234.56
  currency: string;    // "EUR"
};

Integers are exact in JavaScript up to 253, which is roughly ninety trillion euros expressed in cents. That’s comfortably above any household balance we’re likely to meet. Addition and subtraction of integers are exact, so balances computed from ten thousand transactions are exactly the sum of those transactions.

Adding two currencies must fail

The second half of the rule is just as important: adding two Money values in different currencies throws an error. Not a warning, not a silent conversion — an error. If you hold US dollars in a broker and euros in your bank, summing the raw numbers produces a figure that means nothing. The only correct way to combine them is an explicit conversion at a dated exchange rate, and the type system forces that conversion to be written down.

This is the kind of bug that tests rarely catch by accident, because test fixtures tend to be all in one currency. Making it impossible by construction is far cheaper than hunting for it later.

Where the Hard Work Moves: Parsing

Using integers doesn’t make the complexity disappear; it pushes it to the edges, where text becomes numbers. Bank statements are wildly inconsistent about how they write amounts:

Ahorreo’s parser turns each of these into an integer without ever passing through a float: it identifies the decimal separator, splits the string, pads the fractional part and combines the pieces as integers. Every format has a round-trip test — parse, format, parse again, and get exactly the same integer back.

Percentages and Rounding, Made Explicit

Some operations can’t stay exact. Your 50% share of €100.01 is €50.005, which isn’t a whole number of cents. Rather than letting a float decide, Ahorreo makes rounding an explicit step at a well-defined point, so the same input always produces the same cent. When a total must reconcile — the pieces of a savings account’s balance must add up to the balance shown on its card — the parts are computed so that they do, instead of each being rounded independently and drifting apart.

Quantities are not money

One exception is worth mentioning. The number of fund units or fractional shares you hold isn’t money: it’s a quantity, and it routinely has more than two decimals. Ahorreo models quantities separately, as fixed-precision decimals with eight places, so a fund position with fractional units is exact without pretending to be a currency amount.

The Payoff

This discipline pays for itself in one very visible way. When you import a statement that has a balance column, Ahorreo can treat the file not as a list of transactions but as a chain: every row’s balance must equal the previous balance plus the row’s amount. With integer cents, that check either holds exactly or it doesn’t — and when it holds, the figures on your screen are provably the same as your bank’s.

It’s a small rule that you write once and then never think about. Which is exactly what a foundation should be.

The same integers carry through the rest of Ahorreo: the ownership percentages on shared accounts are applied to cents, and the mortgage calculator on this site keeps its schedule in whole cents too. How the rest of the app keeps your data on your device is in Privacy you can test.

This article explains how our software works and the maths behind it. It is not financial, tax or legal advice: for decisions about your own money, check with a qualified professional.