Skip to content

Add a description about formatting decimal.Decimal with the e/E symbol by __format__. #142019

@1nftf

Description

@1nftf
Test output
# Compare different precision on Decimal.
format(Decimal('0'), ".0E")      '0E+0'
format(Decimal('0'), ".1E")      '0.0E+1'
format(Decimal('0'), ".2E")      '0.00E+2'
format(Decimal('0'), ".3E")      '0.000E+3'
format(Decimal('0'), ".30E")     '0.000000000000000000000000000000E+30'
format(Decimal('-0.0'), ".0E")   '-0E-1'
format(Decimal('-0.0'), ".1E")   '-0.0E+0'
format(Decimal('-0.0'), ".2E")   '-0.00E+1'
format(Decimal('-0.0'), ".3E")   '-0.000E+2'
format(Decimal('-0.0'), ".30E")  '-0.000000000000000000000000000000E+29'

# Compare different 0 values.
format(0, ".3E")                '0.000E+00'
format(0.0, ".3E")              '0.000E+00'
format(-0.0, ".3E")             '-0.000E+00'
format(0j, ".3E")               '0.000E+00+0.000E+00j'
format(Decimal('0'), ".3E")     '0.000E+3'
format(Decimal('-0.0'), ".3E")  '-0.000E+2'

# Compare different format methods.
format(Decimal('0'), ".30E")                          '0.000000000000000000000000000000E+30'
format(1234512345123451234512345, ".30E")             '1.234512345123451205320704000000E+24'
format(Decimal('1234512345123451234512345'), ".30E")  '1.234512345123451234512345000000E+24'
"%.30E" % (Decimal('0'),)                             '0.000000000000000000000000000000E+00'  # exponent is 0, because the value is converted to float first
"%.30E" % (1234512345123451234512345,)                '1.234512345123451205320704000000E+24'
"%.30E" % (Decimal('1234512345123451234512345'),)     '1.234512345123451205320704000000E+24'

NOTE: The internal exponent of the Decimal, rather than its sign, is causing the difference.
It seams that precision will affect exponent.

  • When formatting Decimal('0'), the exponent is equal to precision.
  • When formatting Decimal('-0.0'), the exponent is equal to precision-1.

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 type
to be consistent with built-in types.

Test script
from decimal import Decimal

templates = (
    'format({v}, ".{p}E")',
    # 'f"{{{v}:.{p}E}}"',
    # '"{{:.{p}E}}".format({v})',
    # '"%.{p}E" % ({v},)',

    # 'format({v}, ".{p}e")'
    # 'f"{{{v}:.{p}e}}"',
    # '"{{:.{p}e}}".format({v})',
    # '"%.{p}e" % ({v},)',
)

values = (
    # 0,
    # 0.0,
    # -0.0,
    # complex(0),
    Decimal('0'),
    Decimal('-0.0'),

    # 0.001,
    # Decimal('0.001'),

    # 1,
    # 1.0,
    # Decimal('1'),

    # 10,
    # 10.0,
    # Decimal('10'),

    # 100,
    # 100.0,
    # Decimal('100'),

    # 1234512345123451234512345,
    # 1234512345123451234512345.0,
    # Decimal('1234512345123451234512345'),
)

precision = (
    0,
    1,
    2,
    3,
    # 4,
    # 5,
    # 6,
    # 7,
    # 8,
    # 9,
    # 10,
    30,
    # 100,
    # 1000,
    # 10000,
)

results = [
    ((expr := t.format(v=repr(v), p=p)), eval(expr))
    for t in templates
        for v in values
            for p in precision
]

max_len_expr = max(len(expr) for expr, val in results)
for expr, val in results:
    print(f"{expr}{' ' * ((max_len_expr)-len(expr))}  {repr(val)}")

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsDocumentation in the Doc dir

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions