2025, Oct 07 01:00
Float to Decimal in Python: why near-zero values aren't rounded to 0, and how Emin=0 fixes create_decimal_from_float
Python Decimal create_decimal_from_float: near-zero floats aren't rounded to 0 since exponent is applied first. Set context Emin=0 to clamp tiny values.
When converting floats to Decimal, it’s tempting to assume that context precision will round values exactly as you expect. With create_decimal_from_float(), that mostly holds — until you hit numbers very close to zero. Then, instead of being rounded to zero, the value may show up in scientific notation as a tiny nonzero quantity. If you’ve ever tried to turn something like sin(180°) into a Decimal and were surprised by a lingering 1e-18, this is the pattern you encountered (Python 3.10).
Problem demo
The precision is set to six digits. A typical value rounds as expected, but a near-zero value does not get clamped to 0:
>>> import decimal
>>> policy = decimal.getcontext()
>>> policy.prec = 6
>>> policy.create_decimal_from_float(0.123456789123456789)
Decimal('0.123457')
>>> policy.create_decimal_from_float(0.000000000000000001)
Decimal('1.00000E-18')
What’s really happening
For the ordinary constructor path Decimal(float_value), the documentation is explicit:
The significance of a new Decimal is determined solely by the number of digits input. Context precision and rounding only come into play during arithmetic operations.
create_decimal_from_float() is different. It uses the active decimal context to apply precision and rounding at creation time. However, there is a key detail: the exponent is taken into account before the precision is applied. Values near zero therefore get a negative exponent first, and only then are digits rounded. That’s why a tiny input shows up as something like 1.00000E-18 instead of being rounded down to 0 under the current precision.
Here’s a minimal illustration with a four-digit precision:
>>> import decimal
>>> rules = decimal.getcontext()
>>> rules.prec = 4
>>> rules.create_decimal_from_float(0.000000000123456789)
Decimal('1.235E-10')
Fix: bound the exponent (Emin)
If the intended behavior is to round away all those near-zero exponents and clamp the representation at the current precision, constrain the exponent range in the context. Specifically, set Emin to 0 so that no exponential notation is used:
>>> import decimal
>>> conf = decimal.getcontext()
>>> conf.prec = 4
>>> conf.Emin = 0
>>> conf.create_decimal_from_float(0.000000000123456789)
Decimal('0.000')
By forcing Emin to 0, you prevent the creation path from representing tiny magnitudes with a negative exponent; the value is then rounded at the given precision and collapses to zero.
Why you should care
Switching between binary floats and Decimal is common when tightening numeric behavior in financial or scientific code. The near-zero edge case can silently misalign expectations about rounding, leaving tiny nonzero values where you expected exact zeros. Understanding how the decimal context applies exponent handling before precision — and how to control it with Emin — ensures your conversion layer matches your rounding policy.
Conclusion
When creating Decimal from float under a context, precision alone doesn’t guarantee that tiny values turn into zeros, because the exponent is processed first. If you want near-zero inputs to round away under your current precision, set context.Emin = 0 before calling create_decimal_from_float(). That keeps numbers out of scientific notation for small magnitudes and aligns the result with the rounding you intended.
The article is based on a question from StackOverflow by Qwerty-uiop and an answer by jsbueno.