Skip to content

'%.2f' %(1/8) does not round correctly #99534

@jowagner

Description

@jowagner

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions