Updated Jun 30, 2026

The Fixes (and When Floats Are Fine)

Here's the good news: you don't need to understand IEEE 754 to a deeper level to be safe. You need a small kit of three habits, matched to the three bites from the last phase. Money gets an exact representation, comparisons get a tolerance, and — the part people forget — you let floats keep doing the jobs they're great at.

Fix #1: store money as integer minor units (cents)

The cleanest money fix is to stop storing fractions at all. A dollar is 100 cents; a price isn't 19.99 dollars, it's 1999 cents. Integers are stored exactly — no binary-fraction problem, because there's no fraction. You do all the math in whole cents and only format with a decimal point when you show it to a human.

price_cents = 1999          # $19.99, stored as a whole number
quantity = 3
total_cents = price_cents * quantity
print(total_cents)                          # 5997 — exact, always
print(f"${total_cents // 100}.{total_cents % 100:02d}")

What just happened: Every value and every operation stays a whole number, so there is no rounding error to accumulate — 1999 * 3 is exactly 5997. The decimal point only appears at the very end, for display. This is what most payment systems do under the hood, and it's the default you should reach for.

The catch to know about: division. Splitting 5997 cents three ways is 1999 each with 0 left over — clean. But 100 cents split three ways is 33, 33, 33 with 1 cent left over, and someone has to get that extra cent. Integer cents doesn't make that decision for you; it makes the leftover visible and exact so you can assign it on purpose instead of losing it to rounding.

Fix #2: use a decimal type when you need fractions of a cent

Sometimes whole cents aren't enough — tax rates, interest, currencies with more than two decimal places, per-unit prices like fuel. For those, most languages offer a decimal type that stores numbers in base 10, exactly, the way you write them. It's slower than a float, but it doesn't round 0.1.

from decimal import Decimal

a = Decimal("0.1") + Decimal("0.2")
print(a)              # 0.3 — exactly
print(a == Decimal("0.3"))

What just happened: Decimal stores 0.1, 0.2, and 0.3 as exact base-10 values, so the sum is exactly 0.3 and the equality check is True. The catch you must respect: build decimals from strings (Decimal("0.1")), not from floats — Decimal(0.1) would copy the float's rounding error straight in. Most languages have an equivalent: BigDecimal, decimal, NUMERIC/DECIMAL in SQL.

⚠️ A decimal type only helps if you keep floats out of it. Decimal("0.1") is exact; Decimal(0.1) inherits the float error and defeats the entire point. Same rule in databases: store money in a DECIMAL/NUMERIC column, never FLOAT/REAL.

Fix #3: compare floats with a tolerance, not ==

When you genuinely are working with floats (you'll see good reasons in a moment), stop asking "exactly equal?" Ask "close enough?" — within a small tolerance that absorbs rounding noise.

a = 0.1 + 0.2
b = 0.3
print(a == b)                       # False — exact comparison
print(abs(a - b) < 1e-9)            # True — within tolerance
import math
print(math.isclose(a, b))           # True — the built-in way

What just happened: abs(a - b) < 1e-9 checks whether the two values differ by less than a tiny threshold, which they don't — so it treats them as equal. Better still, many languages ship a ready-made isclose that handles the tricky scaling for big and small numbers. The rule: never == two computed floats; compare the difference against a tolerance (often called an epsilon).

📝 Terminology. Minor units are the smallest whole unit of a currency (cents for USD). A decimal type stores numbers in base 10 exactly. Tolerance / epsilon is the small allowed difference when comparing floats for "close enough."

The other half of the truth: floats are often exactly right

It would be wrong to leave you afraid of floats. They're not a mistake to be avoided — they're the correct tool for a huge class of problems. Reach for floats freely when:

  • The values are measurements, not exact counts. Temperatures, distances, sensor readings, weights — these are already approximate; a float's tiny error is far smaller than the real-world uncertainty.
  • You're doing graphics, audio, simulation, or geometry. Pixel positions, 3D coordinates, physics steps, signal samples. Speed matters enormously and a 16th-digit error is invisible on screen or in the ear.
  • You're doing science or statistics. Averages, models, probabilities — float's range and speed are exactly what you want, and you compare with tolerances anyway.
USE A FLOAT FOR:        DON'T USE A FLOAT FOR:
  measurements            money / currency
  graphics & geometry     exact counts that must reconcile
  physics & simulation    anything compared with ==
  science & statistics    accumulating sums where exactness matters

What just happened: The dividing line isn't "floats are bad," it's exactness. Need an exact decimal value (especially money) or an exact comparison? Use integers or a decimal type. Working with inherently approximate quantities where speed counts? Floats are the right call. The same property that makes them wrong for dollars makes them perfect for pixels.

💡 Key point. One rule covers ninety percent of the danger: never store money in a float. Use integer minor units, or a decimal type. Everything else here is detail hanging off that rule.

💬 For builders: when you pick up a new language, spend five minutes learning its money story before you write any. Which decimal type does it ship? What's the idiomatic isclose? What column type does the database use for currency? Knowing those three answers up front is the difference between a clean ledger and a 2 a.m. reconciliation bug. And if the underlying base-2 idea still feels shaky, Why Math Isn't Your Enemy and How a Computer Actually Works are worth a pass.

[
  {
    "q": "What's the recommended default for storing a USD price like $19.99 in code?",
    "choices": [
      "A float: 19.99",
      "A string: \"19.99\"",
      "An integer number of cents: 1999",
      "A double rounded to two places"
    ],
    "answer": 2,
    "explain": "Integer minor units (cents) are stored exactly — no binary-fraction rounding. You only add the decimal point when formatting for display."
  },
  {
    "q": "When using a decimal type, why must you write Decimal(\"0.1\") instead of Decimal(0.1)?",
    "choices": [
      "Strings are faster than numbers",
      "Decimal(0.1) inherits the float's rounding error before the decimal type ever sees it",
      "Decimal can't accept numbers at all",
      "It's only a style preference with no real effect"
    ],
    "answer": 1,
    "explain": "0.1 as a float is already rounded. Passing that float into Decimal copies the error in. Building from the string \"0.1\" gives the exact value you wrote."
  },
  {
    "q": "Which task is a GOOD fit for floats?",
    "choices": [
      "A bank account balance",
      "3D coordinates in a graphics engine",
      "An invoice total that must reconcile to the cent",
      "A loop counter compared with == to a target"
    ],
    "answer": 1,
    "explain": "Graphics values are inherently approximate and speed-critical, where a 16th-digit error is invisible. Money and exact comparisons need integers or a decimal type."
  }
]

← Phase 2: Where It Bites · Guide overview

Check your understanding 3 questions

1. What's the recommended default for storing a USD price like $19.99 in code?

2. When using a decimal type, why must you write Decimal("0.1") instead of Decimal(0.1)?

3. Which task is a GOOD fit for floats?

Was this page helpful?