skip to content

Why is Math (and double in general) the wrong tool for money, and what causes results like 0.1 + 0.2 != 0.3?

level: middleimportance: should knowfreq 45%

answer

  1. double = IEEE-754 binary: only sums of powers of two are exact
  2. 0.1 has no finite binary form → stored approximate (like 1/3 in decimal)
  3. 0.1 + 0.2 == 0.30000000000000004, != 0.3
  4. money → BigDecimal (from String!) + explicit RoundingMode, or integer cents
  5. never == on computed doubles; use an epsilon if you must

basics

~20 s

double stores numbers in binary, and many decimals like 0.1 can't be represented exactly, so tiny errors creep in (0.1 + 0.2 comes out as 0.30000000000000004). For money use BigDecimal, which stores exact decimal values.

solid answer

~50 s

Math operates on double, which is an IEEE-754 binary floating-point type. It represents values as a sign, a binary mantissa, and an exponent — so only numbers expressible as a finite sum of powers of two are exact. Decimal fractions like 0.1, 0.2, 0.3 have no exact binary form, just as 1/3 has no finite decimal form, so they're stored as the nearest representable approximation. The errors accumulate: 0.1 + 0.2 yields 0.30000000000000004, and == comparisons of computed doubles fail. For currency this is unacceptable — rounding errors become real money discrepancies. The right tool is BigDecimal, which stores an exact unscaled integer plus a decimal scale, and forces you to specify a RoundingMode. Never compare doubles with ==; compare within an epsilon tolerance, or better, model exact quantities with BigDecimal (or integer minor units like cents).

go deeper

for a junior

Knows decimals like 0.1 aren't exact in double and that money should use BigDecimal, not double.

for a middle

Explains binary representation (sum of powers of two), why 0.1+0.2 drifts, and to compare with epsilon, not ==.

for a senior

Chooses BigDecimal-from-String + RoundingMode or integer minor units, and reasons about error accumulation across many operations.

for a principal

Sets a money-handling standard across services (representation, rounding mode, serialization), and identifies where double is acceptable (measurements) vs forbidden (financial).

## What `double` actually is The methods on `Math` take and return `double`. A `double` is an **IEEE-754 64-bit binary floating-point** number. It stores a value as three parts: a **sign**, a **mantissa** (the significant digits, in binary), and an **exponent** (a power of two). In other words, a `double` can only represent values that are a finite sum of **powers of two** (like 1/2, 1/4, 1/8...). ## Why 0.1 can't be stored exactly In the decimal system, 1/3 = 0.3333... never terminates — you can't write it exactly with finitely many digits. The **same problem happens in binary** for many ordinary decimals. The number 0.1 in binary is `0.0001100110011...` repeating forever. Since a `double` has only 52 bits of mantissa, it must **truncate** to the nearest representable value — a tiny bit off from true 0.1. So `0.1`, `0.2`, and `0.3` are each stored as close-but-not-exact approximations. When you compute `0.1 + 0.2`, the two approximations add to a value that prints as **`0.30000000000000004`**, which is *not* the stored approximation of `0.3`. Hence: ```java System.out.println(0.1 + 0.2); // 0.30000000000000004 System.out.println(0.1 + 0.2 == 0.3); // false ``` This isn't a Java bug — it's inherent to binary floating point and behaves the same in virtually every language. ## Why this rules out `double`/`Math` for money Money needs **exact decimal** arithmetic: $0.10 + $0.20 must be exactly $0.30, and rounding must follow a precise, auditable rule. With `double`, the small representation errors **accumulate** over many operations, and you can end up a cent off — a real, sometimes regulatory, problem. `Math.round` on a `double` doesn't save you because the error is already baked into the value before you round. ## The correct tools **`BigDecimal`** stores a number as an exact **unscaled integer** plus a **scale** (number of digits after the decimal point) — so `0.1` is stored as exactly `1` with scale `1`. It is exact for decimal values and **forces you to choose a `RoundingMode`** (e.g. `HALF_EVEN`, the banker's-rounding default for finance) whenever a result needs rounding. Construct it from a **String** (`new BigDecimal("0.1")`), never from a `double` (`new BigDecimal(0.1)` would inherit the binary error). **Integer minor units** are an alternative: store money as a whole number of cents (a `long`) and do exact integer math, formatting to dollars only for display. ## Never compare computed doubles with `==` Because results carry tiny errors, `==` on computed `double`s is unreliable. If you must use `double`, compare with a small tolerance (an **epsilon**): `Math.abs(a - b) < 1e-9`. But for anything where exactness matters, switch to `BigDecimal` or integer units rather than chasing epsilons. ## Takeaway `Math`/`double` is for *measurements and scientific math* where a tiny relative error is fine (lengths, physics, graphics). It is the **wrong** tool for *exact decimal quantities* like money — use `BigDecimal` (from a String, with an explicit RoundingMode) or integer minor units.

  • Why construct BigDecimal from a String rather than a double?
    new BigDecimal(0.1) takes the already-imprecise double value and stores all its binary error (0.1000000000000000055...). new BigDecimal("0.1") parses the exact decimal and stores precisely 0.1.
  • Is the 0.1 + 0.2 issue specific to Java?
    No. It's inherent to IEEE-754 binary floating point, so it appears in C, Python, JavaScript, and nearly every language using doubles. The fix (decimal types / integer units) is universal too.

saying these in an interview costs you the question

  • Calling 0.1 + 0.2 != 0.3 a 'Java bug' (it's standard IEEE-754 behavior everywhere).
  • Using double/float (and Math.round) for currency.
  • Comparing computed doubles with == instead of an epsilon tolerance.
  • Building BigDecimal from a double literal, re-introducing the binary error.

context