Skip to content

How to Fix Rounding Errors in Python Calculations

Problem

When I calculate the cube root of 64 in Python, I got this result:

>>> 64 ** (1/3)
3.9999999999999996

I expected exactly 4.0, not 3.9999999999999996.

This problem shows up everywhere:

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False

Environment

  • Python 3.11
  • macOS 14.6
  • Working on financial calculations and data analysis

What happened?

I was building a script that needed to calculate cube roots and compare floating-point numbers. The math seemed straightforward:

# Calculate cube root
result = 64 ** (1/3)
# Check if result equals expected value
if result == 4.0:
print("Perfect cube root")
else:
print(f"Got {result}")

But the condition never evaluated to True. The cube root calculation returned 3.9999999999999996 instead of 4.0.

I also saw this issue when adding decimal numbers:

total = 0.1 + 0.2
if total == 0.3:
print("Equal")
else:
print(f"Not equal: {total}")
# Output: Not equal: 0.30000000000000004

This caused bugs in my comparison logic and test assertions.

Why does this happen?

The issue is that computers store floating-point numbers in binary format. Some decimal numbers can’t be represented exactly in binary:

  • 0.1 in decimal becomes a repeating fraction in binary
  • 0.2 has the same problem
  • When you add them, the small errors compound

Python uses the IEEE 754 standard for floating-point arithmetic. This standard trades exact precision for speed and memory efficiency. Hardware-level floating-point operations are fast, but they’re not infinitely precise.

For most calculations, these tiny errors don’t matter. But they cause problems when:

  • You compare floats with ==
  • You need exact decimal precision (like money)
  • Errors accumulate over many operations

How to solve it?

Solution 1: The round() function

For quick precision control, I tried rounding the result:

result = round(64 ** (1/3), 5)
print(result) # 4.0
if result == 4.0:
print("Equal")

This works for display purposes and reducing precision to a manageable number of decimals.

But I noticed a gotcha:

>>> round(2.5)
2
>>> round(3.5)
4

Python uses “banker’s rounding” - it rounds to the nearest even number. This avoids bias in statistical calculations, but it surprised me the first time I saw it.

Also, round() doesn’t fix the underlying floating-point representation. It just reduces the visible precision:

>>> round(0.1 + 0.2, 5)
0.3
>>> (0.1 + 0.2) == 0.3
False # Still False!

So round() is good for display, but not for equality checks.

Solution 2: math.isclose() for comparisons

For comparing floating-point numbers, I found math.isclose():

import math
result = 0.1 + 0.2
if math.isclose(result, 0.3):
print("Equal") # This works!

The math.isclose() function checks if two numbers are within a tolerance. It has two parameters:

  • rel_tol: Relative tolerance (percentage-based)
  • abs_tol: Absolute tolerance (fixed margin)

Default values are rel_tol=1e-09, abs_tol=0.0. You can adjust them:

# Cube root comparison
result = 64 ** (1/3)
if math.isclose(result, 4.0, rel_tol=1e-9):
print(f"Cube root is 4.0 (within tolerance)")
# For larger numbers
big_result = 1000.1 + 2000.2
if math.isclose(big_result, 3000.3, rel_tol=1e-9, abs_tol=1e-6):
print("Close enough")

This is the right tool for:

  • Unit tests with floating-point assertions
  • Conditional logic comparing floats
  • Scientific calculations where exact equality isn’t required

Solution 3: Decimal module for money

When I worked on financial calculations, I learned that floating-point is dangerous for money:

# DANGEROUS
price = 0.1 + 0.2
total = price * 3
print(total) # 0.9000000000000001

This could cause accounting errors. The solution is the decimal module:

from decimal import Decimal, getcontext
# Set precision
getcontext().prec = 28
price = Decimal('0.1') + Decimal('0.2')
total = price * 3
print(total) # 0.9 exactly

Critical rule: Always use strings when creating Decimals:

# WRONG - Float already has error baked in
Decimal(0.1)
# Decimal('0.1000000000000000055511151231257827021181583404541015625')
# RIGHT - String preserves exact value
Decimal('0.1')
# Decimal('0.1')

For currency, you also want proper rounding:

from decimal import Decimal, ROUND_HALF_UP
amount = Decimal('10.995')
rounded = amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(rounded) # 11.00 (proper rounding)

The quantize() method rounds to a specific number of decimal places. ROUND_HALF_UP is what most people expect (5 rounds up).

Solution 4: fractions.Fraction for exact math

When I need exact rational arithmetic, I use the Fraction class:

from fractions import Fraction
# Floats have errors
result = 1/3 + 1/3 + 1/3
print(result) # 0.9999999999999999
# Fractions are exact
result = Fraction(1, 3) + Fraction(1, 3) + Fraction(1, 3)
print(result) # 1
print(float(result)) # 1.0

Fractions store numbers as numerators and denominators. They’re perfect for:

  • Mathematical proofs and symbolic computations
  • Working with ratios and probabilities
  • When you need to preserve the exact rational form

The trade-off is performance - Fractions are slower than floats.

Solution 5: Format strings for display

Sometimes the issue is just cosmetic. When I need to display numbers to users, I use format strings:

value = 3.9999999999999996
# f-strings (Python 3.6+)
print(f"{value:.2f}") # 4.00
# format()
print("{:.3f}".format(value)) # 4.000
# Percentage
print(f"{value:.1%}") # 400.0%

This doesn’t change the underlying value. It just changes how it’s displayed:

value = 3.9999999999999996
print(f"{value:.2f}") # 4.00
print(value) # 3.9999999999999996

Use format strings when:

  • You’re generating user-facing output
  • Building reports or dashboards
  • The underlying precision doesn’t matter for the display

Which solution should I use?

Here’s a quick reference:

ScenarioSolutionWhy
Display formattingf-stringsCosmetic only, fast
Equality checksmath.isclose()Handles tolerance properly
Money/financeDecimalLegally required precision
Exact rational mathFractionPreserves exact values
General roundinground()Quick precision control

I think of it as a decision tree:

Is this for display only?
├─ Yes → Use f-strings or format()
└─ No → Is this financial/money?
├─ Yes → Use Decimal module
└─ No → Are you comparing floats?
├─ Yes → Use math.isclose()
└─ No → Do you need exact rational values?
├─ Yes → Use fractions.Fraction
└─ No → Use round() for precision

Common pitfalls to avoid

I’ve made these mistakes - learn from them:

Pitfall 1: Comparing floats directly

# WRONG
if x == y:
pass
# RIGHT
if math.isclose(x, y):
pass

Pitfall 2: Creating Decimal from float

# WRONG - Float already has error
Decimal(0.1)
# RIGHT - String preserves exact value
Decimal('0.1')

Pitfall 3: Accumulating rounding errors

# Problem: Errors compound
total = 0
for _ in range(100):
total += 0.1
print(total) # 9.999999999999983
# Solution: Use Decimal
from decimal import Decimal
total = Decimal('0')
for _ in range(100):
total += Decimal('0.1')
print(total) # 10.0

Performance considerations

I ran some benchmarks to compare performance:

  • float: Fastest, hardware-accelerated
  • round(): Minimal overhead
  • math.isclose(): Negligible overhead
  • Decimal: 10-100x slower than float
  • Fraction: Slowest, symbolic computation

The rule I follow: use floats by default, only upgrade to Decimal or Fraction when necessary. Profile before optimizing prematurely.

Summary

In this post, I showed how to fix rounding errors in Python floating-point calculations. The key point is choosing the right tool for your use case:

  • round() for quick precision control
  • math.isclose() for safe float comparisons
  • Decimal for financial calculations
  • Fraction for exact rational math
  • Format strings for display purposes

Floating-point errors are unavoidable in binary representation, but Python provides good tools to handle them. Choose based on your specific problem, not convenience.

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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments