9
>>> a = 0.3135
>>> print("%.3f" % a)
0.314
>>> a = 0.3125
>>> print("%.3f" % a)
0.312
>>>

I am expecting 0.313 instead of 0.312 Any thought on why is this, and is there alternative way I can use to get 0.313?

Thanks

1
  • 5
    Search for IEEE 754 and round to even. The rounding is not wrong. It conforms to the standard. Commented Aug 27, 2013 at 19:11

6 Answers 6

10

Python 3 rounds according to the IEEE 754 standard, using a round-to-even approach.

If you want to round in a different way then simply implement it by hand:

import math
def my_round(n, ndigits):
    part = n * 10 ** ndigits
    delta = part - int(part)
    # always round "away from 0"
    if delta >= 0.5 or -0.5 < delta <= 0:
        part = math.ceil(part)
    else:
        part = math.floor(part)
    return part / (10 ** ndigits) if ndigits >= 0 else part * 10 ** abs(ndigits)

Example usage:

In [12]: my_round(0.3125, 3)
Out[12]: 0.313

Note: in python2 rounding is always away from zero, while in python3 it rounds to even. (see, for example, the difference in the documentation for the round function between 2.7 and 3.3).

Sign up to request clarification or add additional context in comments.

5 Comments

Fails on: my_round(4354788000,-9) -> 3999999999.9999995
@VincentAlex I'm pretty sure I wrote this code 8 years ago assuming ndigits >= 0. Anyway the issue is that you are hitting float limits. If you check the code you end up on the return line of my_round with 4 / (10 ** (-9)) which should return the correct result but it doesn't. You can easily check for the sign of ndigits and use part * 10 ** abs(ndigits) for the negative cases and this seems to work better.
according to the wikipedia page you've provided, the IEEE 754 standard defines several rounding rules. among them is ties away from zero, which is what i've been taught since primary school. i challenge anyone in this world to provide a counterexmple where ties to even is either useful or intuitive.
@Yvon The standard says (as is quoted on Wikipedia): "Round to nearest, ties to even" is the default for binary floating point and the recommended default for decimal. "Round to nearest, ties to away" is only required for decimal implementations. Speak with the IEEE committee if you want to complain that this messes up your primary school teachings. In any case this is also called "Unbiased or banking rounding". As the name implies it has good properties when you are adding up sequence of numbers by avoiding bias that you introduce when rounding half down or half up.
3

If you need accuracy don't use float, use Decimal

>>> from decimal import *
>>> d = Decimal('0.3125')
>>> getcontext().rounding = ROUND_UP
>>> round(d, 3)
Decimal('0.313')

or even Fraction

Comments

1

Since round() doesn't work correctly, I used the LUA approach instead:

from math import floor
rnd = lambda v, p=0: floor(v*(10**p)+.5)/(10**p)

Testing:

>>> round( 128.25, 1 )
128.2
>>> rnd( 128.25, 1 )
128.3
>>>

To not conflate with other answers
Other tests include:

>>> rnd = lambda v, p=0: round(v*(10**p))/(10**p) # hairetdin's answer
>>> rnd( 128.25, 1 )
128.2
>>> print( '%.3f' % round(128.25,1) ) # Tushar's answer
128.200

Other details:

The duplicate processing of 10**p is negligible in performance from storing and referencing a variable.

Comments

0

try

print '%.3f' % round(.3125,3)

Comments

0

I had the same incorrect rounding

round(0.573175, 5) = 0.57317

My solution

def to_round(val, precision=5):
    prec = 10 ** precision
    return str(round(val * prec) / prec)

to_round(0.573175) = '0.57318'

Comments

0

Here's an implementation of away-from-zero.

  • Works for both positive and negative numbers.

  • Not optimized for performance.

  • Matches a few error behaviors of the built-in implementation. Output will be int if ndigits is not given, None or 0

import math

def round(number, ndigits=None):
    if not hasattr(number, '__round__'):
        raise TypeError(f'type {type(number)} doesn\'t define __round__ method')
    if not (ndigits==None or type(ndigits)==int)):
        raise TypeError(f'{type(ndigits)} object cannot be interpreted as an integer')
    if ndigits in [None, 0]:
        x = math.floor(.5+abs(number))
        y = int(math.copysign(x, number))
    else:
        m = 10**ndigits
        x = math.floor(.5+abs(number)*m)
        y = math.copysign(x/m, number)
    return y
# end

Define your own language about "the standard" and decide whichever from the multiple IEEE definitions should be implemented by default. This won't change my expectation that the output of round(6.5) should be 7. The GNU C library implements all variants and lets the user of the language to pick. I can't believe Python has chosen to simply pass through whatever is implemented by the underlying library and says no more.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.