简体   繁体   中英

Python Decimal formatting wrong behavior?

I'm trying to obtaining a coherent value when formatting a Decimal with '%.2f'. However the results are surprising to me. Same decimal value, different rounding. Any idea what I'm doing wrong ?

>>> from decimal import Decimal
>>> x = Decimal('111.1650')
>>> print '%.2f' % x
111.17
>>> y = Decimal('236.1650')
>>> print '%.2f' % y
236.16

Thank you

Python's percent-style formatting doesn't understand Decimal objects: when you format, there's an implicit conversion to float. It just so happens that the nearest representable binary float to your x value is this:

>>> print Decimal(float(x))
111.1650000000000062527760746888816356658935546875

That's a touch greater than the 111.165 halfway case, so it rounds up. Similarly, for y , the value that ends up being formatted is this one:

>>> print Decimal(float(y))
236.164999999999992041921359486877918243408203125

In that case, the value being formatted for output is just below the halfway value, so it rounds down.

To avoid the implicit conversion, you can use the .format formatting method:

>>> "{0:.2f}".format(Decimal('111.1650'))
'111.16'
>>> "{0:.2f}".format(Decimal('236.1650'))
'236.16'

Note that you still might not like all the results, though:

>>> "{0:.2f}".format(Decimal('236.1750'))
'236.18'

This style of formatting uses the round-half-to-even rounding mode by default. In fact, it takes the rounding mode from the current Decimal context, so you can do this:

>>> from decimal import getcontext, ROUND_HALF_UP
>>> getcontext().rounding=ROUND_HALF_UP
>>> "{0:.2f}".format(Decimal('236.1750'))
'236.18'
>>> "{0:.2f}".format(Decimal('236.1650'))  # Now rounds up!
'236.17'

As a general comment, being able to implement custom formatting for user-defined classes is one of the big wins for the new-style .format formatting method over the old-style % -based formatting.

Your problem is repeatable. As a workaround, you could use the newer libraries.

    >>> '{0:.2f}'.format( Decimal('236.1650') )
    '236.16'
    >>> '{0:.2f}'.format( Decimal('111.1650') )
    '111.16'

This happens because float values cannot be represented exactly. Refer to the documentation floating point arithmetic.

Or, to give you a clue as to what happens, enter:

>>> Decimal(111.1650)
Decimal('111.1650000000000062527760746888816356658935546875')
>>> Decimal(236.1650)
Decimal('236.164999999999992041921359486877918243408203125')

The problem is that the floating point numbers are not exact near the .005 boundary in your case. You just met the case that one number will be slightly above .005 (therefore ceiled) and the other number is slightly below and gets floored.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM