Back to Blog
Python

Python Decimal vs Float: Choosing the Right Numeric Type

python decimal vs float: Compare Python's float and decimal types: when binary rounding errors appear, how the decimal module fixes them, and which type fits your use...

decimal modulefloating pointnumeric precisionPython typesfinancial calculationsrounding
Illustration comparing Python's float binary representation with the decimal module's exact decimal arithmetic.

When a Python calculation returns 0.30000000000000004 instead of 0.3, the cause is binary floating point, not a bug in your code. The decimal module exists to avoid that class of error. The choice between python decimal vs float comes down to what you value more: exact decimal arithmetic or speed and memory efficiency.

Why float Produces Unexpected Results

Python's float type follows the IEEE 754 binary64 format. It stores every value as a mantissa and an exponent in base 2. Many decimal fractions, such as 0.1, cannot be represented exactly in binary, just as 1/3 cannot be represented exactly in decimal. The value stored is the closest binary approximation.

print(0.1 + 0.2) # 0.30000000000000004

The result is close to 0.3 but not equal to it. For many applications the difference is harmless. For financial calculations, invoicing, or tax computation, it can produce rounding errors that accumulate across thousands of operations.

How the decimal Module Changes the Calculation

The decimal module implements decimal floating point arithmetic. It stores the coefficient and exponent in base 10, so values like 0.1 are represented exactly. The module also gives you control over precision and rounding behavior through a context object.

from decimal import Decimal a = Decimal("0.1") b = Decimal("0.2") print(a + b) # 0.3

Note that the strings are passed to Decimal. Passing a float instead, as in Decimal(0.1), converts the binary approximation first and reproduces the same error you were trying to avoid.

The default context has 28 significant digits of precision. You can change that with localcontext:

from decimal import localcontext, Decimal with localcontext() as ctx: ctx.prec = 3 print(Decimal("1") / Decimal("3")) # 0.333

The context also defines rounding modes such as ROUND_HALF_UP, ROUND_DOWN, and ROUND_CEILING. Choosing the right mode matters when you must match a legal or accounting rule.

A Practical Money Calculation

A typical currency calculation shows where decimal earns its place. Suppose you need to compute an invoice total with a tax rate and round to cents.

from decimal import Decimal, ROUND_HALF_UP price = Decimal("19.99") quantity = Decimal("3") tax_rate = Decimal("0.08") subtotal = price * quantity tax = (subtotal * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) total = subtotal + tax print(subtotal, tax, total) # 59.97 4.80 64.77

The quantize method rounds to a fixed number of decimal places using the specified rounding mode. With float, the same calculation would carry binary noise into the tax and total, and you would need to round manually at every step to keep the numbers clean.

Performance and Memory Tradeoffs

Decimal arithmetic is significantly slower than float arithmetic because each operation goes through the decimal context machinery and allocates new objects. Float operations run on hardware floating-point units and are effectively free by comparison. Decimal values also consume more memory per number.

If you are processing millions of sensor readings, geometric coordinates, or statistical samples, float is the right tool. The precision loss is bounded and rarely matters when the data itself is approximate. If you are summing thousands of currency amounts, the cost of decimal is justified because the correctness requirement is absolute.

Mixing float and Decimal Safely

Python does not allow implicit mixing of float and Decimal in arithmetic operations. Adding them raises a TypeError:

from decimal import Decimal try: result = Decimal("1.5") + 0.5 except TypeError as exc: print(exc) # unsupported operand type(s) for +: 'decimal.Decimal' and 'float'

This is deliberate. Allowing implicit conversion would reintroduce the binary approximation error. When you need to combine the two, convert explicitly and be aware of what you are converting. Decimal(str(float_value)) preserves the decimal representation of the float's string form, which is often what you want for display purposes, but it is still an approximation of the original binary value.

Choosing the Right Numeric Type

Use float when:

  • The data is inherently approximate, such as measurements, coordinates, or probabilities.
  • Performance and memory usage dominate the requirements.
  • You are interoperating with C libraries, NumPy, or other systems that assume binary floats.

Use Decimal when:

  • You are handling money, taxes, interest, or any value that must round according to a legal rule.
  • You need reproducible decimal rounding across platforms.
  • The number of operations is small enough that the performance cost is irrelevant.

A common mistake is to use Decimal for everything "just to be safe." That adds complexity and slows down code that never needed exact decimal semantics. The decision should follow the data, not a general preference.

Compatibility and Storage Considerations

Decimal values do not serialize to JSON natively. The standard json module raises a TypeError when it encounters a Decimal. You need a custom encoder or must convert to string first. Databases differ in how they store decimal values: most SQL databases have a NUMERIC or DECIMAL column type that maps cleanly to Python's Decimal, while float columns map to binary floats.

When you read a decimal value from a database, prefer converting it with Decimal(str(value)) rather than Decimal(value) if the driver returns a float, to avoid importing binary noise into the decimal value. This matters most in financial systems where the database is the source of truth and every intermediate representation must stay exact.

python decimal vs float: Practical Usage and Code Examples | RYUSLOG DEV