Why is Math (and double in general) the wrong tool for money, and what causes results like 0.1 + 0.2 != 0.3?
answer
- double = IEEE-754 binary: only sums of powers of two are exact
- 0.1 has no finite binary form → stored approximate (like 1/3 in decimal)
- 0.1 + 0.2 == 0.30000000000000004, != 0.3
- money → BigDecimal (from String!) + explicit RoundingMode, or integer cents
- never == on computed doubles; use an epsilon if you must
basics
~20 sdouble 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 sMath 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
Knows decimals like 0.1 aren't exact in double and that money should use BigDecimal, not double.
Explains binary representation (sum of powers of two), why 0.1+0.2 drifts, and to compare with epsilon, not ==.
Chooses BigDecimal-from-String + RoundingMode or integer minor units, and reasons about error accumulation across many operations.
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.