Python Floating-Point Precision: Why Your Math is Wrong (And How to Fix It)
The “Broken Calculator” Problem
When I first started learning Python, I wrote a simple calculation:
>>> 0.1 + 0.2 == 0.3FalseWhat? That’s not right. Let me check what Python actually calculated:
>>> 0.1 + 0.20.30000000000000004I thought I broke something. But then I saw a Reddit post where someone calculated the cubic root of 64:
>>> 64 ** (1/3)3.9999999999999996They expected exactly 4.0, not this weird 3.999… number. This wasn’t just me - Python math seemed broken.
Let me see what Python actually stores when I type 0.1:
>>> print(f"{0.1:.60f}")0.100000000000000005551115123125782702118158340454101562500000That’s not 0.1 at all. Python thinks I typed this long messy number.
Why Computers Can’t Do Simple Math
After digging into this, I found that the problem isn’t Python - it’s how computers represent numbers.
We humans use base-10 (decimal). We write 0.1 as one tenth. Simple.
Computers use base-2 (binary). They only have 0s and 1s. Here’s what happens when I try to represent 0.1 in binary:
Decimal: 0.1Binary: 0.0001100110011001100110011001100110011001100110011...The pattern “0011” keeps repeating forever. Just like 1/3 = 0.333… in decimal, some decimal fractions become infinite in binary.
Computers can’t store infinite digits. They have to cut off somewhere. When Python stores 0.1, it actually stores:
Binary: 0.0001100110011001100110011001100110011001100110011001101 └──────────────────────────────────────────────────┘ 52-bit precision limitThat last digit gets rounded, and that tiny error propagates through calculations.
This isn’t a bug. It’s the IEEE 754 standard - a specification that tells computers how to store floating-point numbers.
IEEE 754: Understanding Binary Floating-Point
Python stores floating-point numbers using the IEEE 754 double-precision format. Here’s what that looks like:
┌─────────┬──────────┬─────────────────────────────────────┐│ Sign │ Exponent │ Mantissa ││ (1 bit) │ (11 bits)│ (52 bits) │└─────────┴──────────┴─────────────────────────────────────┘Three parts:
- Sign bit: 0 for positive, 1 for negative
- Exponent: Like scientific notation (tells us where the decimal point goes)
- Mantissa: The actual digits (precision)
Double precision means 64 bits total. That gives Python about 15-17 decimal digits of precision.
Let me check my system’s float precision:
>>> import sys>>> print(f"Decimal digits of precision: {sys.float_info.dig}")Decimal digits of precision: 15So Python guarantees about 15 significant decimal digits. Beyond that, rounding errors show up.
This is why 0.1 + 0.2 gives me 0.30000000000000004 - the error is in the 17th digit, beyond the guaranteed precision.
Common Floating-Point Pitfalls
I’ve run into several situations where floating-point precision causes real problems.
Pitfall 1: Direct Comparison with ==
When I compare floats directly, I get wrong results:
>>> 0.1 + 0.2 == 0.3FalseThis fails because of the tiny precision error. Instead, I use tolerance-based comparison:
>>> import math>>> math.isclose(0.1 + 0.2, 0.3)TrueThe math.isclose() function checks if two numbers are close enough within a tolerance.
I can also specify my own tolerance:
>>> abs((0.1 + 0.2) - 0.3) < 1e-9TruePitfall 2: Accumulation Errors
When I add the same number repeatedly, errors compound:
>>> total = 0>>> for _ in range(10):... total += 0.1>>> print(total)0.9999999999999999I expected 1.0, but got 0.999… Each addition added a tiny error, and those errors accumulated.
Here’s what happened step by step:
0.1+ 0.1 = 0.2 (theoretically) = 0.2000000000000000111... (in binary)
+ 0.1 = 0.3 (theoretically) = 0.3000000000000000444... (in binary)
... after 10 additions = 0.9999999999999999Pitfall 3: Currency Calculations
This is dangerous. Using floats for money can give wrong results:
>>> price = 19.99>>> tax_rate = 0.0825>>> total = price * (1 + tax_rate)>>> print(f"${total:.2f}")$21.64But what’s the actual value?
>>> print(f"{total:.20f}")21.63917499999999892308The true value should be 21.639175. If I round this for financial calculations, I might charge the wrong amount or report incorrect totals.
Python Solutions: Float vs Decimal vs Fraction
Python gives me three main tools for handling numbers with precision requirements.
Solution 1: math.isclose() for Comparisons
For most comparison cases, math.isclose() works well:
>>> import math>>> result = 0.1 + 0.2>>> if math.isclose(result, 0.3):... print("Equal within tolerance")Equal within toleranceI can adjust the tolerance for my needs:
>>> math.isclose(1.000001, 1.0, rel_tol=1e-6)True
>>> math.isclose(1.000001, 1.0, rel_tol=1e-9)FalseUse math.isclose() for:
- General floating-point comparisons
- Testing scientific calculations
- Checking if sensor readings are within expected range
Solution 2: decimal.Decimal for Financial/Math
When I need exact decimal representation (like money), I use the decimal module:
>>> from decimal import Decimal, getcontext>>> getcontext().prec = 28 # Set precision
>>> price = Decimal('19.99')>>> tax = Decimal('0.0825')>>> total = price * (Decimal('1') + tax)
>>> print(total)21.639175Notice: I initialize Decimal with strings, not floats. If I use a float, I bring in the floating-point error:
>>> # WRONG - brings in float error>>> Decimal(0.1)Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> # RIGHT - exact representation>>> Decimal('0.1')Decimal('0.1')Here’s my financial calculator example:
from decimal import Decimal, ROUND_HALF_UP
def calculate_invoice(subtotal, tax_rate, discount=0): """Safe financial calculation using Decimal""" subtotal = Decimal(str(subtotal)) tax_rate = Decimal(str(tax_rate)) discount = Decimal(str(discount))
tax = subtotal * tax_rate discounted = subtotal - discount total = discounted + tax
# Round to 2 decimal places (cents) return total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
>>> total = calculate_invoice(100.00, 0.0825, 5.00)>>> print(f"${total}")$103.25Use Decimal for:
- Financial calculations
- Currency operations
- Legal/compliance requirements
- When exact decimal representation matters
But there’s a performance cost. Decimal is slower than float:
>>> import timeit
>>> # Float - fast>>> timeit.timeit('0.1 + 0.2', number=1000000)0.03 seconds
>>> # Decimal - slower but precise>>> timeit.timeit("Decimal('0.1') + Decimal('0.2')",... setup='from decimal import Decimal',... number=1000000)0.8 secondsDecimal is about 20-30x slower than float, but for financial calculations, correctness matters more than speed.
Solution 3: fractions.Fraction for Rational Numbers
When I need exact rational arithmetic, I use Fraction:
>>> from fractions import Fraction>>> one_third = Fraction(1, 3)
>>> one_third * 3Fraction(1, 1) # Exactly 1!
>>> one_third + one_third + one_thirdFraction(1, 1) # Perfect!No rounding errors. Fractions are exact.
Use Fraction for:
- Mathematical proofs
- Exact rational arithmetic
- Symbolic math
- When denominators are important
Here’s a comparison:
| Type | Precision | Speed | Use Case |
|---|---|---|---|
| float | ~15-17 digits | Fastest | General, scientific |
| Decimal | Configurable | 20-30x | Financial, currency |
| Fraction | Exact (rational) | 100x | Math proofs, exact rational |
Real-World Examples
Example 1: E-commerce Shopping Cart
For a shopping cart, I always use Decimal:
from decimal import Decimal
def calculate_cart(items): """Safe cart calculation with Decimal""" subtotal = Decimal('0') for item in items: price = Decimal(str(item['price'])) quantity = Decimal(str(item['quantity'])) subtotal += price * quantity return subtotal
# Bad: float>>> 0.1 + 0.2 + 0.30.6000000000000001
# Good: Decimal>>> Decimal('0.1') + Decimal('0.2') + Decimal('0.3')Decimal('0.6')Example 2: Scientific Measurements
For sensor data, I use math.isclose():
import math
def sensor_reading_valid(reading, expected): """Use isclose for sensor data""" return math.isclose(reading, expected, rel_tol=1e-5)
>>> sensor_reading_valid(0.30000000000000004, 0.3)TrueSensors have inherent imprecision, so a small tolerance is appropriate.
Example 3: Graphics/Game Development
For position comparisons, I define an explicit epsilon:
def positions_equal(pos1, pos2, epsilon=1e-6): return abs(pos1 - pos2) < epsilonGame positions are floats, and exact equality rarely matters.
Advanced: Machine Epsilon
I wanted to understand the limits of float precision, so I looked at machine epsilon:
>>> import sys>>> sys.float_info.epsilon2.220446049250313e-16This is the smallest number that, when added to 1.0, produces a result different from 1.0.
Let me test this:
>>> 1.0 + 1e-16 == 1.0True # Too small to matter
>>> 1.0 + 1e-15 == 1.0False # Large enough to registerMachine epsilon tells me the smallest meaningful difference I can represent with a float. Any error smaller than this gets lost.
Best Practices Checklist
After working through all of this, here’s what I do:
DO ✓:
- Use
math.isclose()for float comparisons - Use
Decimalfor money/currency - Set explicit tolerance values
- Document precision requirements
- Initialize Decimal from strings
- Understand my domain requirements
DON’T ✗:
- Never compare floats with
== - Don’t use float for financial calculations
- Don’t initialize Decimal with floats:
Decimal(0.1)is wrong - Don’t ignore precision warnings
- Don’t assume
round()fixes everything
Summary
In this post, I explained why 0.1 + 0.2 != 0.3 in Python. The key points are:
- Computers use binary (base-2), while we use decimal (base-10)
- Some decimal fractions like 0.1 are infinite in binary
- IEEE 754 double-precision provides ~15-17 decimal digits of accuracy
- Use
math.isclose()for float comparisons - Use
Decimalfor financial calculations - Use
Fractionfor exact rational arithmetic - Never compare floats with
==
The floating-point “error” isn’t a bug - it’s a fundamental limitation of how computers represent numbers. Understanding this helps me write more reliable code and choose the right tool for each job.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Python Floating Point Arithmetic: Issues and Limitations
- 👨💻 What Every Computer Scientist Should Know About Floating-Point Arithmetic
- 👨💻 IEEE 754-2019 Standard for Floating-Point Arithmetic
- 👨💻 Python decimal module documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments