Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 717
synclabs Avatar asked Dec 26 '22 23:12

synclabs


2 Answers

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.

like image 69
Mark Dickinson Avatar answered Jan 04 '23 21:01

Mark Dickinson


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'
like image 28
superdud Avatar answered Jan 04 '23 21:01

superdud