
Stop Using Floats for Money: The Integer Approach
Stop losing money to floating-point errors. Learn why floats fail and how to use integers, cents, and Decimal types to handle currency safely in code.
If you are building an e-commerce platform, a billing system, or a fintech application, storing and calculating currency using standard floating-point numbers is a reliable way to lose money and fail compliance audits. Today, we will explore why floats fail us when handling money, and how to fix it using the robust Integer (Cents) approach.
The Root of the Problem: IEEE 754
If you've been writing JavaScript (or almost any other mainstream language) for a while, you have probably run into the classic interview question: Why does 0.1 + 0.2 equal 0.30000000000000004?
Often, we laugh this off as a quirky language feature. Under the hood, most modern programming languages (including JavaScript, Python's default float, and Java's double) use the IEEE 754 standard for floating-point arithmetic. This standard represents numbers in base-2 (binary).
While base-2 is incredibly efficient for computers, it struggles with certain base-10 (decimal) fractions. Just as the fraction 1/3 cannot be represented perfectly in decimal (it becomes 0.3333...), fractions like 1/10 (0.1) or 1/5 (0.2) become infinitely repeating decimals in binary.
When the computer tries to store these repeating binary fractions in a limited amount of memory (usually 64 bits for double precision), it has to round them off. For a single operation, this rounding error is microscopic. But money doesn't just sit there; it gets multiplied by tax rates, divided among stakeholders, and summed across millions of transactions.
When Microscopic Errors Accumulate

Imagine you run a SaaS platform and you need to calculate an 8.25% tax on a $19.99 subscription.
If you use floats, the math might yield a number with many decimal places. You round it to two decimal places for the invoice. Now imagine you process 100,000 of these transactions a month. Because of the tiny floating-point inaccuracies during intermediate steps—especially when adding up a large array of these already-imperfect numbers to generate a monthly revenue report—your total might be off by several dollars or even hundreds of dollars.
In the world of accounting, a ledger must balance perfectly down to the penny. If your database says you collected $1,000,000.05 but your payment gateway reports $1,000,000.00, your system is technically broken.
The Universal Solution: Use Integers (The Cents Approach)

The most bulletproof and universally applicable way to handle money is to stop using fractions entirely. Instead of storing dollars (or Euros, or Pounds) as floats, you store the smallest subunit of the currency as an integer.
For US Dollars, that subunit is the cent. So, $19.99 is stored in your database and memory as 1999.
Why Integers Work
Integers do not suffer from the base-2 fractional representation issue. 1999 + 100 is exactly 2099. You can sum millions of integer values and the result will be perfectly accurate every single time.
Here is what this looks like in practice using TypeScript:
// ❌ BAD: Using floats
function calculateTotalFloat(price: number, quantity: number, taxRate: number) {
const subtotal = price * quantity;
const tax = subtotal * taxRate;
return subtotal + tax;
}
// calculateTotalFloat(19.99, 3, 0.0825) -> might yield floating point dust
// ✅ GOOD: Using integers (cents)
function calculateTotalCents(priceInCents: number, quantity: number, taxRate: number): number {
const subtotal = priceInCents * quantity;
// We still use float for the tax rate, but we immediately round the resulting
// money amount to the nearest cent
const tax = Math.round(subtotal * taxRate);
return subtotal + tax;
}
Notice that we still use a float for the taxRate (like 0.0825), but the crucial step is using Math.round() (or Math.floor(), depending on your legal jurisdiction's tax rules) immediately after the multiplication to get back to a safe integer before any further addition.
Database Schema
In your database, you simply use an INTEGER or BIGINT column.
CREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price_cents BIGINT NOT NULL,
currency VARCHAR(3) DEFAULT 'USD'
);
Using BIGINT is highly recommended over standard integers, especially if you are dealing in currencies where transaction values can quickly exceed the 32-bit integer limit (~2.1 billion).
The Fowler "Money" Pattern
Storing just the price_cents is good, but what happens when your application needs to support multiple currencies? Adding 1000 USD cents and 1000 EUR cents together is a recipe for disaster.
Martin Fowler introduced the "Money" pattern, which encapsulates both the amount and the currency in a single object.
class Money {
constructor(
public readonly amount: number, // in smallest subunit
public readonly currency: string // ISO 4217, e.g., 'USD'
) {
if (!Number.isInteger(amount)) {
throw new Error("Amount must be an integer");
}
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error("Cannot add different currencies");
}
return new Money(this.amount + other.amount, this.currency);
}
}
const price = new Money(1999, "USD");
This pattern prevents accidental cross-currency math and enforces integer usage at the boundary.
Alternative: Decimal Types
If you don't want to use integers, your other option is to use exact Decimal types. Many databases support a DECIMAL or NUMERIC column type (e.g., DECIMAL(10, 2) means 10 total digits, 2 of which are after the decimal point).
These database types do exact base-10 math under the hood. However, you must pair them with a matching exact decimal library in your application code. In JavaScript/TypeScript, the native Number cannot handle this safely. You must use a library like decimal.js, big.js, or bignumber.js.
import Decimal from "decimal.js";
const price = new Decimal("19.99");
const taxRate = new Decimal("0.0825");
const tax = price.times(taxRate).toDecimalPlaces(2, Decimal.ROUND_HALF_UP);
const total = price.plus(tax);
Pros of Decimals: It maps more closely to human thinking (19.99 instead of 1999).
Cons of Decimals: It requires importing a library, it's significantly slower than native integer math, and it's easy for a junior developer to accidentally convert the Decimal object back to a native JS float, re-introducing the bug.
Formatting at the Edges
If you use the Integer approach, remember to only convert to a decimal format at the very edge of your application—right before displaying it to the user. Modern browsers and Node.js have built-in support for this via the Intl.NumberFormat API.
function formatCentsToUSD(cents: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(cents / 100);
}
console.log(formatCentsToUSD(1999)); // "$19.99"
Summary
Floating-point numbers are fantastic for physics simulations, graphics processing, and scientific calculations. They are fundamentally incompatible with finance.
For your next project, do your future self (and your accounting department) a favor: treat money as a discrete, countable entity. Use integers to represent the smallest subunit, wrap it in a strong type or object to track currency, and sleep well knowing your ledgers will always balance perfectly.
written by
Nguyên Tech
Responses
Loading comments…