DEV Community

Cover image for Stop storing money as a float
Akshay Verma
Akshay Verma

Posted on

Stop storing money as a float

I'm building an expense-splitting app for couples. Early on, I stored every amount as a double. It worked until two people split a ₹1,000.05 grocery bill three ways — and the app insisted someone still owed ₹0.01 that nobody could pay.

Why it happens

0.1 + 0.2 === 0.30000000000000004. Binary floating point can't represent most decimal fractions exactly, the same way decimal can't write 1/3. Individually the error is invisible. Across a month of bills, summed and re-split, it surfaces as a phantom rupee.

The fix: integers, always

Store the smallest unit — paise, not rupees:

const toPaise = (rupees) => Math.round(rupees * 100);
const format = (paise) => ₹${(paise / 100).toFixed(2)};

Every amount in the database is an integer. Addition and subtraction are now exact. You convert to a display string at the very last moment, and never compute on that string.

Splitting without losing a paisa

Division is where money actually disappears. ₹1000.05 three ways is 33335 paise each with 0 left, but ₹1000.04 leaves a remainder of 1. Rounding each share independently either invents or destroys money.

Distribute the remainder explicitly instead:

function split(totalPaise, weights) {
const sum = weights.reduce((a, b) => a + b, 0);
const shares = weights.map(w => Math.floor(totalPaise * w / sum));
let left = totalPaise - shares.reduce((a, b) => a + b, 0);
for (let i = 0; left > 0; i = (i + 1) % shares.length, left--) shares[i]++;
return shares;
}

The shares now always sum to exactly the total. Someone absorbs an extra paisa — but it's deliberate, and it's visible.

The rule

Integers for storage and arithmetic. Strings only for display. Never a float, and never a rounded intermediate value.

The bug that started this took an evening to find and three lines to fix. Money is the one place where "close enough" is a bug report.

Top comments (0)