Repository navigation
Add a description about formatting decimal.Decimal with the e/E symbol by __format__. #142019
Description
Activity
- changed the title
[-]When format decimal.Decimal('0') to string, the exponent of result is not 0.[/-][+]When format decimal.Decimal('0') to scientific notation with given precision, the exponent of result is not 0.[/+]on Nov 27, 2025 Decimalinstances differ from floats in that they have significant trailing zeros. For example,Decimal("10"),Decimal("10.0")and"Decimal("1e1")are distinct (although equal under==) and the difference affects the output of some operations. So the base exponent of aDecimalinstance is an extra piece of information beyond just the numerical value.To the extent that it's possible, formatting tries to preserve this extra information. That's the reason for the behaviour you're seeing here.
>>> x = Decimal('0') >>> s = format(x, '.2e') >>> s '0.00e+2' >>> y = Decimal(s) >>> x.as_tuple() == y.as_tuple() True
Above we've reconstructed
yfrom its formatted representation, and the reconstructedyhas exactly the same internal representation asxhad. Ifswere"0.00e+0"instead, we would have lost the exponent information.In addition to the above behavior, the exponent is not padded to
two digits, which also makes it inconsistent with the built-in types.This is a separate issue: I'd recommend opening a separate tracker issue if you want to pursue this. But this behaviour is mandated by the specification that the
decimalmodule implements, so changing it would be a tough sell. From the spec (emphasis mine):this comprises the letter ‘E’ followed immediately by the adjusted exponent converted to a character form. The latter is in base ten, using the characters 0 through 9 with no leading zeros, always prefixed by a sign character
Note that the spec itself does not prescribe how
.e-style formatting should work: it describes two operations:to-scientific-string, which roughly corresponds tog-style formatting (but without precision control), andto-engineering-string, which is likee-style formatting (again with no precision control) but with the displayed exponent constrained to be a multiple of 3.So there's some reading-between-the-lines necessary to implement full-fledged float-style formatting from Decimal.
But the spirit of preserving the exponent information is present in the
to-engineering-stringdescription. E.g., in theto-engineering-stringspecification:if the number is a zero, the zero will have a decimal point and one or two trailing zeros added, if necessary, so that the original exponent of the zero would be recovered by the to-number conversion.
Decimal represents not only an approximate value, but also its precision.
Decimal('0')is not the same asDecimal('0.000'). The former represents a value betweenDecimal('-0.5')andDecimal('0.5'), the latter -- betweenDecimal('-0.0005')andDecimal('0.0005'). Formatting tries to preserve this property.>>> format(Decimal('0'), ".2E") '0.00E+2' >>> Decimal('0.00E+2') Decimal('0') >>> format(Decimal('0.000'), ".2E") '0.00E-1' >>> Decimal('0.00E-1') Decimal('0.000')
So this is not a bug, but a feature.
- addedpendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
on Nov 28, 2025 This issue is more about formatting values with the e/E symbol (with a given precision),
rather than converting the value to a scientific or engineering string.When converting/serializing, you might want to keep as much information as possible.
When formatting, you usually intend to display them in a uniform format.
# These format(Decimal('0.0'), ".3E") '0.000E+2' f"{Decimal('0.0'):.3E}" '0.000E+2' "{:.3E}".format(Decimal('0.0')) '0.000E+2' "%.3E" % (Decimal('0.0'),) '0.000E+00' # the Decimal is implicitly converted to a float # Instead of these Decimal('0.0').to_eng_string()) '0.0' getcontext().to_eng_string(Decimal('0.0')) '0.0' getcontext().to_sci_string(Decimal('0.0')) '0.0'For users who are not familiar with the Decimal class (like me), it
might be expected that Decimal behaves similarly to float.
"0.000E+2" rather than "0.000E+00" is indeed a bit strange, and it
took me a little time to find the reason.Documents compare
These two documents only define how to convert decimal floating-point
numbers to strings, not how to format them.The formatting behavior is defined in these documents.
- https://docs.python.org/3/library/string.html#format-specification-mini-language
- https://en.cppreference.com/w/c/io/fprintf
(I think printf behavior can be seen as the de facto standard for c style formatting.)
The cpp document requires the exponent to be 0 when the value is 0,
and requires the exponent to be padded to two digits. These is also
the behavior of python float.However the python document omitted above requirements.
I'm not sure if this is intentionally relaxed for Decimal, or the
Decimal.__format__implementer only followed the simplified python
document.Proposal
My opinion is that we can add a more detailed description of the
different behavior.But if possible, we can make Decimal's format behavior more similar to
float, and then update the documentation to describe the printf-like
behavior.
P.S. I never said this is a bug. The tag I gave this issue from the
beginning was #docs, not #type-bug .- changed the title
[-]When format decimal.Decimal('0') to scientific notation with given precision, the exponent of result is not 0.[/-][+]"Add a note about formatting decimal.Decimal with the e/E symbol."[/+]on Nov 28, 2025 - changed the title
[-]"Add a note about formatting decimal.Decimal with the e/E symbol."[/-][+]Add a note about formatting decimal.Decimal with the e/E symbol.[/+]on Nov 28, 2025 I think this might be closed.
Though, maybe new issue should be opened, about minimum number of digits in the exponent for 'e'/'g' formats. I don't think that the decimal spec forbids here using the printf-style formatting also for Decimal's. The Decimal constructor also accepts padding zeros in the exponent. The only question is: does it worth a backward compatibility break?
But a non-zero exponent will make the padding less useful.
And the '%' formatting will yield a different result due to implicit conversion to float.>>> format(1 / Decimal('inf'), ".2e") '0.00e-1000024' >>> "%.2e" % (1 / Decimal('inf')) '0.00e+00'
If current Decimal format behavior is considered a feature and will
not change in future. A description like these can be added.When the value is equal to zero, the exponent is always zero for float,
and internal exponent plus number of digits after decimal point for
Decimal.Comment posted on the wrong issue
I discovered the formatting difference while investigating #142019
I found this document first:
https://docs.python.org/3/library/string.html#format-specification-mini-language.When I tried to find another document, I searched for '% format'
and '% formatting' but found nothing related to
https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting.I misunderstood that there is only one document about formatting,
and that the basic functionalities of the format-specification mini-language are also applicable to % formatting,
except for some additional functions.Perhaps a note could be added at the beginning of
https://docs.python.org/3/library/string.html#format-specification-mini-language
stating that this is not suitable for % formatting, along with a link back to it."5 remaining items
- removedpendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
on Nov 30, 2025 I think I can complete this part and make it unambiguous.I've already spent a lot of time on it.As current observed behavior
(only checked on windows, most of x64 releases, some of x86 releases):- If
floatvalue equal to 0, the exponent is also 0 - If
Decimalvalue equal to 0, the exponent might not be 0 floatwill padding exponent to 2digitsDecimalalways have not padding exponent
- If
After figuring all this out, I will create a PR and highlight the key routines.I have little to no experience with real-world C projects, thus need an experienced contributor to help me review it.I will create a PR and highlight the key routines.
Please don't. Could you just modify your pr to include a simple note, as suggested above: "For float the exponent always contains at least two digits."?
This is what most users would expect:
For float, the exponent always contains at least two digits.
I would like to add description like this:
For a given Decimal object
obj, ifobj == 0, the exponent will be
obj.as_tuple()[2] + pinstead of always being 0.@skirpichev
If you’re on board with this, I’ll go ahead and change the PR.- changed the title
[-]Add a note about formatting decimal.Decimal with the e/E symbol.[/-][+]Add a description about formatting decimal.Decimal with the e/E symbol by __format__.[/+]on Dec 1, 2025 See this comment for aggregated information.
Sorry for my verbose comment and bad layout and some mistakes,
made this issue a little off topic.I have already made a lots of fold and revision, to make more
easy to read.For a given Decimal object obj, if obj == 0, the exponent will be obj.as_tuple()[2] + p instead of always being 0.
I'm not sure if this is a good idea. There is nothing special in handling zero for Decimals, as it was explained above. Maybe: "For float the exponent always contains at least two digits and it's zero if the value is zero."
- added a commit that references this issue
on Dec 16, 2025
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsTodo
Test output
NOTE: The internal exponent of the Decimal, rather than its sign, is causing the difference.
It seams that precision will affect exponent.
In addition to the above behavior, the exponent is not padded to
two digits, which also makes it inconsistent with the built-in types.
Although the results are numerically correct, and the document
(https://docs.python.org/3/library/string.html#format-specification-mini-language)
does not limit the exponent when the coefficient is 0.
However, this can be confusing for users.
Maybe the document needs to add a description.
note, or change the result of Decimal typeto be consistent with built-in types.
Test script
Linked PRs