Floating Point Arithmetic: Why 0.1 + 0.2 Is Not 0.3
Open a browser console and type 0.1 + 0.2. You get 0.30000000000000004. Every developer encounters this at some point and most move on with a vague discomfort about numbers in programming. The fix is usually “round it” or “use a library.” But understanding why it happens is worth the five minutes it takes.
Counting in Binary
Computers store numbers in binary - base 2. Integers are straightforward: 42 in binary is 101010. Every integer can be represented exactly.
Fractions are different. In decimal (base 10), some fractions terminate and some don’t. 1/4 = 0.25, terminates. 1/3 = 0.333…, repeats forever.
In binary, the same thing happens - but with different fractions. The fractions that terminate in binary are those that can be expressed as a sum of powers of 2: 1/2, 1/4, 1/8, 1/16.
1/10 cannot be expressed this way. In binary, 0.1 decimal is a repeating fraction: 0.0001100110011001100… repeating forever. The computer has to stop somewhere and round.
The IEEE 754 Standard
Modern computers use IEEE 754 double-precision floating point to represent decimal numbers. The format stores a number as:
value = sign × mantissa × 2^exponent
64 bits total: 1 for sign, 11 for exponent, 52 for the fractional mantissa. This gives you about 15-17 significant decimal digits of precision.
The 52-bit mantissa means the computer stores the closest representable binary fraction to the number you typed. For 0.1:
0.1 in IEEE 754: 0.1000000000000000055511151231257827021181583404541015625
Not 0.1 exactly - the nearest representable value. For 0.2:
0.2 in IEEE 754: 0.200000000000000011102230246251565404236316680908203125
When you add them, you add those two slightly-off values. The result, rounded to the nearest representable float, is not exactly 0.3.
Seeing It More Clearly
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> 0.3
0.3
>>> format(0.3, '.20f')
'0.29999999999999998890'
Even 0.3 is not exactly 0.3. It’s the nearest representable float. The reason 0.1 + 0.2 doesn’t equal 0.3 is that the rounding error in the addition produces a slightly different float than the rounding error in the literal 0.3.
The Practical Consequences
Equality comparisons with floats are dangerous:
total = 0.0
for _ in range(10):
total += 0.1
total == 1.0 # False
total # 0.9999999999999999
Accumulation makes it worse. Each operation introduces a small rounding error. Chained operations chain the errors.
Display doesn’t show the full picture. Most languages display floats with limited digits, hiding the imprecision. print(0.1) shows 0.1, not the full IEEE 754 value.
Where This Actually Bites
Financial calculations. If you represent money as a float and add up many small amounts, the rounding errors accumulate into real cents. This is why financial software uses either integer arithmetic (store cents as integers, not dollars as floats) or decimal types.
# Wrong
price = 0.10
tax = 0.03
total = price + tax # 0.13000000000000001
# Right - use Python's Decimal
from decimal import Decimal
price = Decimal('0.10')
tax = Decimal('0.03')
total = price + tax # Decimal('0.13')
Sorting and bucketing. If you’re grouping values by range and using float comparisons to determine which bucket, you can get surprising edge cases.
Loop termination. Using a float as a loop counter and checking for equality is a reliable way to produce infinite loops or missed iterations.
# Unreliable
x = 0.0
while x != 1.0:
x += 0.1 # might overshoot 1.0 and loop forever
The Correct Approaches
For money: use integers or decimal types. Store prices in cents (integers). Use Decimal in Python, BigDecimal in Java, or a purpose-built money library. Never use float for monetary values.
For comparisons: use epsilon or tolerance. Instead of a == b, check abs(a - b) < epsilon where epsilon is a small tolerance appropriate to your domain.
def approximately_equal(a, b, epsilon=1e-9):
return abs(a - b) < epsilon
approximately_equal(0.1 + 0.2, 0.3) # True
Most languages have a built-in for this: Python has math.isclose(), which also handles relative tolerance for large numbers.
For scientific computation: understand the precision. Double-precision gives you about 15-17 significant digits. If your algorithm requires more precision than that, you need arbitrary-precision arithmetic or a reformulation of the algorithm.
Why Not Fix It
The reasonable question is: why do computers use a format that causes this? Why not just represent decimals exactly?
The answer is performance and range. IEEE 754 arithmetic maps directly to hardware instructions and runs at full CPU speed. Exact decimal arithmetic requires software emulation, which is slower by orders of magnitude and has different tradeoffs (representing 1/3 exactly requires knowing when to stop).
For the vast majority of numeric computation - scientific work, graphics, statistics, machine learning - floating point is exactly the right tool. The precision is more than sufficient and the speed matters. The only domain where it genuinely fails is financial calculation, where humans think in decimal and small errors compound into real money.
The behavior of floats is not a bug. It is the predictable consequence of the representation. Once you know the representation, the behavior is never surprising again.