Numeric Precision in Python

Learn how Python handles numeric precision across integers, floats, and complex numbers. Understand floating-point limitations and how to use Decimal for exact arithmetic.

5 min read

Python numeric precision is not a single concept applied uniformly across all number types. It is three different guarantees that depend on which numeric type you are using. Integers promise unlimited precision with no rounding and no overflow. Floats promise hardware-accelerated speed with the tradeoff that decimal fractions are stored as binary approximations that can accumulate small errors. The Decimal type from the standard library promises exact decimal arithmetic at the cost of slower computation. Understanding these three different precision models and knowing when to use each one is what separates programmers who write reliable numeric code from those who discover floating-point errors only after their calculations have gone wrong.

If you have read about the integer data type, the float data type, and complex numbers in Python, you already know the basics of each type's behavior. This article focuses specifically on the precision question: how much you can trust the numbers that Python gives you, where the limits are, and what tools Python provides to push those limits when your application demands more accuracy than the defaults can deliver. The classic example that introduces every discussion of floating-point precision is that 0.1 plus 0.2 does not exactly equal 0.3 in Python, and understanding why that happens and how to handle it is the gateway to writing numerically robust code.

Integer precision: unlimited and exact

Python integers are the safest numeric type from a precision standpoint because they have no fixed size and perform no implicit rounding. In languages like C, Java, and JavaScript, integers have a fixed bit width, typically 32 or 64 bits, and calculations that exceed that width either wrap around silently or produce undefined behavior. Python 3 removed these limits entirely. An integer can grow to thousands of digits if your algorithm requires it, and every arithmetic operation on integers produces an exact result with no rounding, no truncation, and no overflow.

This unlimited precision comes from Python's internal representation of integers as arrays of 30-bit digits in base two-to-the-thirty, a technique known as arbitrary-precision arithmetic or bignum arithmetic. Integer operations are implemented in software rather than in a single CPU instruction, which makes them slower per operation than fixed-size integers in compiled languages. For the vast majority of applications, this speed difference is irrelevant because integer arithmetic is rarely the bottleneck. The safety of knowing your calculations will never silently overflow is almost always worth the small performance cost.

The only way to lose precision with integers is to deliberately convert them to floats. When you pass a very large integer to the float constructor, Python converts it to the nearest representable IEEE 754 double, which can only store about 15 to 17 significant decimal digits exactly. An integer with 30 digits will be rounded to fit into the float's 53-bit mantissa. This rounding is silent, and recovering the original integer from the float is impossible because information has been discarded. If you need to work with large exact values, keep them as integers and use integer arithmetic throughout.

Float precision: IEEE 754 double-precision

Python floats are implemented using the IEEE 754 double-precision binary floating-point format that is built into virtually every modern processor. This format represents numbers as a sign bit, an 11-bit exponent, and a 53-bit mantissa, which together provide about 15 to 17 decimal digits of precision and a range from roughly 10 to the power of negative 308 to 10 to the power of 308. The hardware executes float operations in a single CPU instruction, making them extremely fast, which is why floats are the default choice for scientific computing, data analysis, and machine learning.

The fundamental limitation of IEEE 754 floats is that they use binary fractions rather than decimal fractions. Just as the fraction one-third cannot be represented exactly in decimal notation, requiring an infinite repeating sequence of threes, fractions like one-tenth cannot be represented exactly in binary notation. When you write 0.1 in Python, the value stored is not exactly one-tenth but the closest binary approximation, which is slightly larger than 0.1. When you add this approximation to the approximation for 0.2, the result is slightly larger than 0.3. The difference is about 5.5 times 10 to the negative 17, which is far below the precision most applications need but large enough to cause direct equality checks to fail.

The standard solution for comparing floats is to use a tolerance rather than direct equality. Python's math module provides the math.isclose function, which checks whether two floats are within a relative or absolute tolerance of each other. For most applications, math.isclose with its default relative tolerance of 10 to the negative 9 is the correct way to compare floats. Using direct equality on floats is almost always a mistake unless you know exactly how the values were computed and can guarantee that the computation is exact.

pythonpython
import math
 
a = 0.1 + 0.2
b = 0.3
print(a == b)
print(math.isclose(a, b))

The first print statement outputs False, which surprises every programmer the first time they encounter it. The second outputs True because math.isclose determines that the values are close enough given the default tolerance. This two-line demonstration encapsulates the entire practical lesson of float precision: never test floats for exact equality, always use a tolerance comparison instead.

The Decimal module for exact decimal arithmetic

When floats are not precise enough, Python's decimal module provides the Decimal class, which stores numbers as decimal digits with a configurable precision. Decimal arithmetic follows the same rules as arithmetic done by hand with pencil and paper. The value 0.1 is stored exactly as one-tenth, not as a binary approximation. Arithmetic on Decimal objects is exact within the configured precision, and rounding modes can be set to match the requirements of financial regulations, accounting standards, or scientific calculations.

Creating Decimal objects requires care because passing a float to the Decimal constructor preserves the float's approximation error. The correct way to create an exact Decimal from a fractional value is to pass a string. Decimal with the string argument "0.1" creates an exact representation of one-tenth. Decimal with the float argument 0.1 creates the Decimal equivalent of the binary approximation that was already in the float, defeating the purpose of using Decimal in the first place. This string-in, string-out pattern for Decimal values is the most common pitfall for programmers new to the module.

Decimal arithmetic is slower than float arithmetic because it is implemented in software rather than hardware, and every operation must track precision and apply the configured rounding mode. For applications that process millions of numbers, this performance difference matters and may require profiling to determine whether Decimal is acceptable. For most business applications that handle financial data, the performance cost is negligible compared with the benefit of never having to explain a missing cent to an auditor.

Complex number precision

Complex numbers inherit the precision characteristics of their component floats. A complex number stores its real and imaginary parts as IEEE 754 double-precision floats, so any precision limitations that apply to floats also apply to the components of a complex number. The magnitude of a complex number, computed by the abs function, is also a float with the same precision constraints. The cmath module's functions for trigonometry, logarithms, and exponentials all operate on complex numbers with floating-point precision.

There is no complex equivalent of the Decimal type in the standard library. If you need arbitrary-precision complex arithmetic, third-party libraries like mpmath provide configurable-precision complex numbers at the cost of performance. For the vast majority of scientific and engineering applications, the 15 digits of precision that complex numbers inherit from floats are more than sufficient because measurement uncertainty in the real world typically limits precision to far fewer digits.

Rune AI

Rune AI

Key Insights

  • Python integers have unlimited precision and never round or overflow.
  • Python floats use IEEE 754 binary format and cannot represent decimal fractions exactly.
  • The classic 0.1 + 0.2 != 0.3 behavior is not a Python bug; it is inherent in binary floating-point.
  • Use math.isclose() for comparing floats with a tolerance instead of direct equality.
  • Use the Decimal module for exact decimal arithmetic when precision matters, such as in finance.
RunePowered by Rune AI

Frequently Asked Questions

Why does 0.1 + 0.2 not equal 0.3 in Python?

Python floats use the IEEE 754 double-precision binary format, which cannot represent decimal fractions like 0.1 exactly, just as decimal notation cannot represent 1/3 exactly. The values 0.1 and 0.2 are stored as the closest possible binary approximations. When added, the small approximation errors combine to produce a result that differs from 0.3 by about 10 to the power of negative 17. This is not a Python bug; it is a fundamental property of binary floating-point arithmetic that affects all languages using IEEE 754 floats.

How can I do exact decimal arithmetic in Python?

Use the Decimal class from Python's decimal module for exact decimal arithmetic. Decimal stores numbers as decimal digits rather than binary fractions, so 0.1 is stored exactly. Decimal arithmetic follows the same rules as hand calculation with pencil and paper. You can set the precision to any number of decimal places. The tradeoff is that Decimal operations are slower than float operations because they are implemented in software rather than hardware.

Do Python integers ever lose precision?

No. Python integers have unlimited precision. They can grow to any number of digits without rounding, truncation, or overflow, limited only by the available memory in your computer. Arithmetic on integers is always exact. The only way to lose precision with integers is to explicitly convert them to floats, at which point the float's 53-bit mantissa limits the representable precision to about 15 to 17 significant decimal digits.

Conclusion

Python gives you three numeric tools with different precision guarantees. Integers are always exact and can grow arbitrarily large. Floats are fast and hardware-accelerated but approximate decimal fractions. The Decimal type is exact for decimal arithmetic at the cost of speed. Choosing the right one depends on whether your application prioritizes speed, exactness, or decimal fidelity. For financial calculations where every cent matters, reach for Decimal. For scientific computing where measurement uncertainty already dominates, floats are the right choice.