Why does 0.1 + 0.2 not equal 0.3 with float/double, and what should you use for money?
answer
- double stores base-2 fractions; 0.1 is non-terminating in binary
- 0.1 + 0.2 = 0.30000000000000004
- Compare floats with epsilon, never ==
- Money: BigDecimal (from String!) or integer cents
- NaN != NaN; use Double.isNaN
basics
~10 sfloat and double store numbers in binary (base 2), and many decimals like 0.1 can't be represented exactly, so tiny rounding errors creep in. For money, use BigDecimal or integer cents, not double.
solid answer
~40 sfloat (32-bit) and double (64-bit) follow IEEE 754, which stores values as sign, exponent, and a base-2 fraction. Decimals such as 0.1, 0.2, and 0.3 have no exact finite binary representation, just as 1/3 has no exact decimal. So 0.1 + 0.2 evaluates to 0.30000000000000004, and `==` against 0.3 fails. The right approaches: never compare floating-point with ==, instead check the absolute difference against a small epsilon; and for exact decimal arithmetic, especially money, use BigDecimal (constructed from a String, not a double) or represent amounts as integer minor units (cents) in a long. double also has special values — positive/negative infinity and NaN — and NaN is not equal to anything, including itself.
code
java · 12 linesSystem.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
// Tolerant comparison:
boolean eq = Math.abs((0.1 + 0.2) - 0.3) < 1e-9; // true
// Money done right:
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3 (exact)
System.out.println(Double.NaN == Double.NaN); // false; use Double.isNaNgo deeper
Knows that decimals can be slightly inexact with double and that money should use BigDecimal, even if unsure why.
Explains base-2 representation as the cause, uses epsilon comparison, and constructs BigDecimal from a String.
Discusses IEEE 754 layout (sign/exponent/mantissa), NaN/infinity semantics, rounding modes, and chooses integer-cents vs BigDecimal per performance needs.
Defines org-wide monetary representation standards, reasons about accumulation error in large aggregations, and weighs BigDecimal cost vs scaled-long pipelines at scale.
## How floating-point numbers are stored `float` (32-bit) and `double` (64-bit) implement the **IEEE 754** standard. Each value is split into three parts: a **sign** bit, an **exponent**, and a **fraction** (also called the mantissa or significand). The value is roughly `sign * fraction * 2^exponent`. The critical word is **2** — the fraction is in *binary* (base 2), not decimal. ## Why 0.1 cannot be stored exactly In decimal, 1/3 = 0.333... never terminates. The same thing happens in binary for many 'nice' decimals: **0.1 in base 2 is a repeating fraction** (0.0001100110011...). With only 52 fraction bits in a double, the value must be **rounded** to the nearest representable binary number. So the `double` you call 0.1 is actually slightly more than 0.1. Same for 0.2 and 0.3 — each is a tiny bit off. When you add the stored-0.1 and stored-0.2, the rounding errors combine, and the result prints as: ``` 0.1 + 0.2 == 0.30000000000000004 ``` which is **not** the stored value of 0.3. Hence `0.1 + 0.2 == 0.3` is `false`. ## Consequences and the right techniques **1. Never compare floating-point with `==`.** Instead compare the magnitude of the difference to a small tolerance (an *epsilon*): ```java boolean equalish = Math.abs(a - b) < 1e-9; ``` **2. For money / exact decimals, do not use double.** Two safe options: - **BigDecimal** — an arbitrary-precision *decimal* type that stores digits in base 10, so 0.1 is exact. **Construct it from a String** (`new BigDecimal("0.1")`), never from a double (`new BigDecimal(0.1)` captures the binary error). Set an explicit scale and `RoundingMode`. - **Integer minor units** — store amounts as whole cents in a `long` (e.g. $12.34 -> 1234) and only format to dollars for display. Fast and exact, but you manage scaling yourself. **3. Special IEEE 754 values.** double/float also represent `Double.POSITIVE_INFINITY` and `NEGATIVE_INFINITY` (e.g. `1.0 / 0.0`), and `NaN` ('Not a Number', e.g. `0.0 / 0.0`). **NaN is not equal to anything, including itself** — `Double.NaN == Double.NaN` is `false`; use `Double.isNaN(x)` to test. There is also a `-0.0` distinct in bits from `+0.0`. ## The bottom line float/double trade exactness for range and speed — perfect for measurements, graphics, and science, wrong for currency and any 'these decimals must add up exactly' requirement. Reach for BigDecimal or integer cents there.
- Why must BigDecimal be built from a String rather than a double?new BigDecimal(0.1) takes the already-imprecise binary double and converts it, preserving the error (0.1000000000000000055...). new BigDecimal("0.1") parses the exact decimal digits, giving precisely 0.1.
- When is double perfectly fine despite this?For measurements, physics/graphics, statistics, and any domain tolerant of ~15 significant digits of relative error, where range and speed matter more than exact decimal equality.
saying these in an interview costs you the question
- Using double for currency calculations
- Comparing floating-point values with == in production code
- Constructing BigDecimal from a double literal (new BigDecimal(0.1)) — it keeps the binary error
- Thinking the error is a JVM bug rather than inherent to IEEE 754 binary representation
- Testing for NaN with x == Double.NaN (always false)