-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Description
Bug report
Rounding with '%.2f' does not work as expected for numbers that are precisely represented in binary.
Minimal, reproducible example:
>>> '%.2f' %0.125
'0.12'
Note that 0.125 is precisely represented in binary as 0.125 = 2**(-3)
and the exponent range of the default float
type is sufficient to cover the exponent -3
.
The discussion of a similar case but with a not precisely represented number in issue #49368 indicates that 0.125 should be rounded up to 0.13.
I understand why issue #49368 is invalid: We expect a loss when decimal numbers in source code are converted to the internal representation. This is not the case here as 0.125 is converted to binary without any loss.
Workaround
In my main use cases, I can work with fractions, e.g. reporting accuracy as count_correct / count_total
. However, %.2f
does not seem to support fractions properly:
>>> '%.2f' %Fraction(1, 8)
'0.12'
Presumably, '%.2f' first converts the fraction to float. (Side note: How about a feature request to fix this?)
Therefore, I wrote the following helper function:
from fractions import Fraction
def my_frac2decimal(n, d, prec = 2):
retval = []
if n*d < 0:
retval.append('-')
n = -n
f = Fraction(n, d)
f += Fraction(5, 10**(prec+1))
# integer part
i = f.numerator // f.denominator
retval.append('%d' %i)
# reduce to 0. fraction
f -= Fraction(i)
while prec > 0:
if '.' not in retval:
retval.append('.')
f = f * Fraction(10)
i = f.numerator // f.denominator
assert 0 <= i <= 9
retval.append('%d' %i)
f -= Fraction(i)
prec -= 1
return ''.join(retval)
This also avoids issues with binary floating point numbers, e.g. for 2.545 from issue #49368
>>> my_frac2decimal(2545, 1000)
'2.55'
Environment
- CPython versions tested on: 3.7.3, 3.6.15 and 2.7.18
- Operating system and architecture: Debian 10 and openSUSE Leap 15.3