10

I'm beginner in Python, and I have one question.
Why does rounding a number like 5.5, 7.5, (anything).5 with odd integer part applying round(num) work correctly (rule 5/4), but rounding number like (anything).5 with non-odd integer part by the same function returns just an integer part? (But if we add a little number like 0.000000001 to that decimal number it works correctly)

I mean the next:

round(9.5)

returns 10, and it's correct. But

round(8.5)

returns 8, and it isn't correct. And

round(8.5 + 0.0000000000001)

returns 9.

Why it works incorrect?
I use Python 3.2.2 at Windows.

FOX 9000
  • 123
  • 1
  • 1
  • 6
Ivan Akulov
  • 4,323
  • 5
  • 37
  • 64

1 Answers1

24

Python 3.x, in contrast to Python 2.x, uses Banker's rounding for the round() function.

This is the documented behaviour:

[I]f two multiples are equally close, rounding is done toward the even choice (so, for example, both round(0.5) and round(-0.5) are 0, and round(1.5) is 2).

Since floating point numbers by their very nature are only approximations, it shouldn't matter too much how "exact" half-integers are treated – there could always be rounding errors in the preceding calculations anyway.

Edit: To get the old rounding behaviour, you could use

def my_round(x):
    return int(x + math.copysign(0.5, x))
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • ["if two \[numbers\] are equally close, rounding is done toward the even choice"](http://docs.python.org/py3k/library/functions.html#round) – Ignacio Vazquez-Abrams Apr 10 '12 at 17:51
  • was just going to post this gem :) – Mike McMahon Apr 10 '12 at 17:53
  • Interesting. Does 2to3 handle this? – Mark Ransom Apr 10 '12 at 17:55
  • @MarkRansom: No. But I think if your code relies on this, it's broken anyway. – Sven Marnach Apr 10 '12 at 17:56
  • And what function I can use to make _normal_ rounding like in Python 2.7? – Ivan Akulov Apr 10 '12 at 18:00
  • @SvenMarnach, sometimes it's not about relying on the results but rather passing the tests, or having your customers complain about a change. – Mark Ransom Apr 10 '12 at 18:01
  • 2
    @gxoptg: try `def roundHalfUp(f): return round(f + 0.00000000000001)` or `def roundHalfUp(f): return math.floor(f + 0.5)` – jedwards Apr 10 '12 at 18:14
  • 1
    `max(round(x), round(x + 1) - 1)` gives incorrect results for negative numbers. – agf Apr 10 '12 at 19:03
  • 4
    To summarise the reason for rounding like this: if `x.5` numbers are a significant portion of some data, rounding them all up shifts the average upwards as well. But if you round half of them up and half down, the average should stay about the same. – Thomas K Apr 10 '12 at 19:30
  • @MarkRansom: No, 2to3 does not deal with bugs in Python 2 being fixed in Python 3. ;-) – Lennart Regebro Apr 11 '12 at 10:06
  • @jedwards: Just as my initial attempt, these solution don't work the same way as the Python 2 `round()` for negative numbers. – Sven Marnach Apr 11 '12 at 12:07
  • The behavior of Python 3.9 seems to differ depending on whether you're rounding to a whole number or to a decimal; definitely some unexpected behavior here: `round(1.05, 1) => 1.1` , `round(1.15, 1) => 1.1` , but `round(10.5) => 10`, `round(11.5) => 12` – Oskar Austegard May 20 '21 at 22:13
  • @OskarAustegard This is due to the nature of floating-point nubmers. The decimal number 1.05 cannot be exactly represented in a binary floating-point number, so it gets rounded to the closest value that can be represented, which happens to be 1.0500000000000000444089209850062616169452667236328125. Since this is slightly more than 1.05, it gets rounded up. Similarly, 1.15 gets rounded to 1.149999999999999911182158029987476766109466552734375 when the decimal string is parsed into a floating-point number, so it gets rounded down. – Sven Marnach May 21 '21 at 07:50
  • The whole thing is kind of moot for this reason. The vast majority of decimal numbers can't be exactly represented as a binary floating-point numbers, so we always deal with rounded numbers. – Sven Marnach May 21 '21 at 07:50
  • Here's how to see what a deicmal numbers gets rounded to: ideone.com/WR6FWU – Sven Marnach May 21 '21 at 07:50