Python Float Usage: Precision, Comparisons, Formatting
python float usage: Understand Python float usage: precision limits, safe comparisons, formatting, conversions, and when to use Decimal.
When you write x = 0.1 in Python, you are not storing the exact decimal value 0.1. You are storing a binary floating-point approximation. This is the first thing to understand about python float usage: every float is a 64-bit double-precision value that follows the IEEE 754 standard. The practical consequences are subtle but show up in arithmetic, comparisons, and formatting. This article explains how floats behave, where they break down, and how to work with them reliably in real code.
How Python Represents Floats Internally
Python floats are always double-precision, meaning they use 64 bits of memory. The IEEE 754 layout splits those bits into a sign, an exponent, and a mantissa (also called significand). The mantissa stores the significant digits, the exponent scales the value, and the sign determines whether the number is positive or negative.
import sys print(sys.float_info)
This prints the machine's float parameters, such as max, min, and epsilon. The epsilon value is the smallest difference between 1.0 and the next representable float. It is about 2.22e-16, which tells you the relative precision you can expect for numbers near 1.0. For larger numbers, the absolute gap between representable floats grows.
Because the mantissa is finite, most decimal fractions cannot be represented exactly. Only fractions whose denominator is a power of two (like 0.5, 0.25, 0.125) have exact binary representations. Everything else is rounded to the nearest representable value.
Precision Limits and the 0.1 + 0.2 Problem
The classic demonstration is 0.1 + 0.2. In decimal, the result is exactly 0.3. In binary, neither 0.1 nor 0.2 is exact, so the sum is also inexact.
print(0.1 + 0.2) # 0.30000000000000004
The result is the closest double to the true sum of the two approximations. This is not a bug in Python; it is a property of binary floating-point arithmetic. Any language that uses IEEE 754 doubles will show the same behavior.
This has direct implications for python float usage in financial calculations, scientific comparisons, and any domain where exact decimal results are expected. You cannot rely on == to compare computed floats, and you cannot assume that rounding to a fixed number of decimal places will always produce the value you expect.
Comparing Floats Safely
Because of representation error, comparing floats with == is usually wrong. Instead, you should compare with a tolerance. Python's math.isclose function is the standard tool.
import math print(math.isclose(0.1 + 0.2, 0.3)) # True
math.isclose uses relative and absolute tolerances. By default, rel_tol is 1e-09 and abs_tol is 0.0. For numbers near zero, you may need to set abs_tol explicitly.
print(math.isclose(1e-20, 0.0, abs_tol=1e-15)) # True
When comparing values that come from different calculations, choose tolerances based on the magnitude of the numbers and the precision you actually need. If you are comparing coordinates in a physics simulation, a relative tolerance of 1e-9 might be appropriate. If you are checking whether a sensor reading is zero, an absolute tolerance is safer.
Formatting Floats for Output
Displaying floats requires care because the default string representation may show many digits or use scientific notation. Python's format spec and f-strings give you control over rounding and presentation.
value = 1234.56789 print(f"{value:.2f}") # 1234.57 print(f"{value:.3e}") # 1.235e+03 print(f"{value:,.2f}") # 1,234.57
The f suffix means fixed-point notation, e means scientific notation, and the comma adds thousands separators. You can also control the total width and alignment, but for most output needs, precision and notation are the key choices.
When you format a float, Python rounds the value to the requested number of digits. This rounding is done on the binary approximation, not on the exact decimal value, so you may occasionally see results like 0.1 formatted as 0.10 when you expected 0.1. That is normal. If you need decimal rounding that matches human expectations, consider using Decimal instead.
Converting Strings and Other Types to Floats
Converting a string to a float is straightforward with float(), but you need to handle invalid input.
s = "3.14159" num = float(s) print(num) # 3.14159
If the string is not a valid number, float() raises ValueError. In production code, you should catch that exception or validate the input first.
def safe_float(value): try: return float(value) except (TypeError, ValueError): return None
float() also accepts integers, other floats, and objects with __float__. It does not accept strings with thousands separators or currency symbols. For those, you need to clean the string or use a library like locale.
When to Use Decimal Instead of Float
For monetary amounts, tax calculations, or any situation where exact decimal arithmetic is required, float is the wrong tool. The decimal module provides arbitrary-precision decimal numbers that behave like the decimal arithmetic you learned in school.
from decimal import Decimal price = Decimal("19.99") tax = Decimal("0.07") total = price * tax print(total) # 1.3993
Notice that you pass strings to Decimal to avoid the binary approximation that would occur if you passed a float. Decimal operations are slower than float operations, but they guarantee correct rounding and avoid the 0.1 + 0.2 problem entirely.
Use Decimal when the decimal representation matters more than performance, such as in financial applications. Use float when you need speed and can tolerate tiny errors, such as in scientific simulations or graphics.
Handling NaN and Infinity
Python floats can represent not-a-number (NaN) and positive or negative infinity. These values arise from operations like 0.0 / 0.0 or 1.0 / 0.0.
nan = float("nan") inf = float("inf") print(nan, inf) # nan inf
NaN is special because it is not equal to itself. You must use math.isnan() to check for it. Similarly, math.isinf() checks for infinity.
import math print(math.isnan(nan)) # True print(math.isinf(inf)) # True
When you receive data from external sources, always validate for NaN and infinity before using it in calculations. A NaN can silently propagate through your code and produce meaningless results. Checking early prevents confusing bugs.
Performance and Memory Considerations
Python floats are objects, not raw machine doubles. Each float object has overhead beyond the 64 bits of data, including a reference count and type pointer. This matters when you store millions of floats in a list. A list of 1 million floats uses significantly more memory than a raw array of doubles. For large numeric workloads, use array('d') or NumPy arrays, which store raw doubles without per-element object overhead.
Arithmetic on Python floats is still fast because the operations are implemented in C. The overhead comes from object allocation and dynamic dispatch. For most applications, the performance difference is negligible. When you need to process large datasets, consider vectorized operations with NumPy to avoid Python-level loops.
In summary, python float usage requires awareness of binary representation, precision limits, and safe comparison techniques. Use math.isclose for comparisons, format floats explicitly, and reach for Decimal when exact decimal arithmetic is non-negotiable. Validate for NaN and infinity when handling external data, and be mindful of memory overhead when storing many floats.