decimal: Decimal(float) Bakes In Binary Floating-Point Imprecision (tested)

decimal.Decimal exists specifically to avoid the rounding surprises of binary floating point. We measured what happens when a Decimal is constructed directly from a float value -- the natural thing to do if a price or rate already exists as a float from JSON, a database driver, or an earlier calculation -- instead of from a string.

1. Decimal(0.1) is not the number 0.1

from decimal import Decimal

print(Decimal("0.1"))
print(Decimal(0.1))
0.1
0.1000000000000000055511151231257827021181583404541015625

Measured: Decimal("0.1") (from a string) is the exact value 0.1. Decimal(0.1) (from the float literal) is a long, ugly number -- because the Python float 0.1 is itself already an approximation (binary floating point can't represent 0.1 exactly), and Decimal() faithfully converts that exact binary approximation into an exact decimal representation of the wrong number. The float imprecision existed the moment 0.1 was written as a float; Decimal() doesn't introduce it, it just makes it visible.

2. The two constructions aren't equal, and arithmetic results differ

from decimal import Decimal

print(Decimal("0.1") == Decimal(0.1))

a = Decimal(0.1) + Decimal(0.2)
b = Decimal("0.1") + Decimal("0.2")
print("from floats:", a)
print("from strings:", b)
print(a == b)
False
from floats: 0.3000000000000000166533453694
from strings: 0.3
False

Measured: the two constructions compare unequal, and summing a pair built from floats gives a different, equally ugly result than summing the string-built pair, which lands exactly on 0.3. Any code path that takes a float (from a JSON payload, an ORM field, a prior float calculation) and wraps it in Decimal() expecting exactness has already lost -- the float's imprecision is locked in before Decimal ever sees it.

3. The fix: construct from a string, or use from_float() if you must start with a float

from decimal import Decimal

price_from_api = 19.99  # arrived as a float
safe = Decimal(str(price_from_api))
print(safe)
19.99

Measured: converting the float to a string first, then to Decimal, gives the clean value a human would expect -- because str() on a float uses Python's shortest-round-trip repr algorithm rather than the exact binary value. This isn't foolproof for every float (some values' shortest string repr still won't be the "intended" decimal value if the float was already computed imprecisely upstream), but it avoids the specific failure mode here. The reliable fix is to never let a value touch float in the first place if it's going to end up in a Decimal -- parse it from a string or an integer-cents representation at the source.

Frequently asked questions

Why is Decimal(0.1) not exactly 0.1 in Python?

Because the float literal 0.1 is already an imprecise binary approximation before Decimal() ever sees it -- we measured Decimal(0.1) producing 0.1000000000000000055511151231257827021181583404541015625, the exact decimal value of that binary approximation, not the number 0.1 itself.

Does Decimal('0.1') equal Decimal(0.1) in Python?

No -- we measured Decimal('0.1') == Decimal(0.1) returning False. The string constructor parses the exact decimal text; the float constructor converts whatever imprecise binary value the float literal actually holds.

How do I safely convert a float to a Decimal without losing precision?

Convert it through str() first, e.g. Decimal(str(my_float)), which we measured giving the clean expected value for a typical price like 19.99. The most reliable fix is avoiding float entirely for values headed into Decimal -- parse from strings or integer cents at the source.

Does arithmetic on Decimal values built from floats give wrong results?

Yes, measurably -- we confirmed Decimal(0.1) + Decimal(0.2) gives 0.3000000000000000166533453694, not the clean 0.3 that Decimal('0.1') + Decimal('0.2') produces, because the float-sourced imprecision propagates through the addition.

Is this a bug in Python's decimal module?

No -- Decimal(float) is documented to convert the float's exact value, and binary floats genuinely can't represent 0.1 exactly. The risk we measured is purely in code that assumes wrapping an existing float in Decimal() retroactively makes it exact, when the imprecision was already baked in before Decimal was involved.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in decimal module: constructed Decimal(0.1) directly from the float literal, compared it against Decimal('0.1') built from the equivalent string, and summed both pairs to see whether the difference affects arithmetic results. PlainHeaders is our extension; Python's decimal module is built in.