builtin round 2.7

Hi there. I just hit a problem in my company, in the process of upgrading from stackless 2.5 to 2.7. Some rounding code, that was (foolishly) using "%.*f" string formatting to achieve floating point rounding started providing different results from before. I explained this away to QA and Developement as a) you shouldn't do that, and b) string representation of floats became better and "more correct" in 2.7. For UI rounding, I directed them to the Decimal module. So far so good. But it appears that the builtin round() method also changed. Whereas I see the changing of floating point representation in string formatting as not being very serious, why did the arithmetic function round() have to change? I don't see this mentioned in the release notes and was initially a bit puzzled by it. Hasn't anyone else been hit by the discrepancy? (and, yes, I know it is now more correct, as seen by round(5.55, 1) (5.6 in py2.5, 5.5 in py 2.7 which is correct since 5.55 is actually 5.549999... ) Perhaps the release notes need updating? K

2010/8/7 Kristján Valur Jónsson <kristjan@ccpgames.com>:
Hi there. [...] But it appears that the builtin round() method also changed. Whereas I see the changing of floating point representation in string formatting as not being very serious, why did the arithmetic function round() have to change?
This was part of the short float repr changes that were backported from 3.1. The round function in 2.7 (on most platforms; barring bugs) always gives correctly rounded results; in 2.6 it was a bit less predictable in halfway cases. One reason to want round to be correctly rounded is to ensure that the repr of the result is always the 'short' one; this means that repr(round(x, 2)) will never produce results like '0.23000000000000001'. If the round function hadn't been updated to be correctly rounded then this wouldn't be true. Another reason is to make sure that string formatting and the round function finally agree with each other: both are doing the same job of rounding to some nearest decimal representation (except that one returns a float, while the other returns a string), so the results should be comparable; the discrepancy between these two operations has confused users in the past. Unfortunately, the agreement isn't quite complete, since round in 2.7 continues to use round-half-away-from-zero for actual exact halfway cases, while string formatting uses round-half-to-even (so e.g. round(0.125, 2) gives (a binary approximation to) 0.13, while format(0.125, '.2f') gives '0.12'). In 3.x they both use round-half-to-even. The only place where people are likely to notice that the round result has changed is in halfway cases, for example round(12.385, 2) (which, of course, thanks to the usual binary floating-point issues, is only actually an approximation to a halfway case). In general, if you're expecting predictable results from *decimal* rounding of *binary* approximations to *decimal* halfway cases then you're asking for trouble. For predictable rounding, use the decimal module. I recently added some text to the floating-point section of the 2.7 tutorial to help explain these round problems.
I don‘t see this mentioned in the release notes and was initially a bit puzzled by it.
True; I don't see it in the whatsnew document either. It's in Misc/NEWS, though. (Search for Issue 7117; there are several entries). Mark

2010/8/7 Mark Dickinson <dickinsm@gmail.com>:
2010/8/7 Kristján Valur Jónsson <kristjan@ccpgames.com>:
Hi there. [...] But it appears that the builtin round() method also changed. Whereas I see the changing of floating point representation in string formatting as not being very serious, why did the arithmetic function round() have to change?
One reason to want round to be correctly rounded is to ensure that the repr of the result is always the 'short' one; this means that repr(round(x, 2)) will never produce results like '0.23000000000000001'. If the round function hadn't been updated to be correctly rounded then this wouldn't be true. [...]
I should also point out that the pre-2.7 round function isn't consistent across common platforms, so it was already dangerous to rely on round results for halfway cases; the 2.7 version of round should be an improvement in that respect. For example, with Python 2.6.6rc1 on OS X 10.6.4, I get the (correct) result:
round(1.0007605, 6) 1.0007600000000001
I'd expect to see the same result on OS X 10.5, on Windows and on 64-bit Linux boxes. But on a 32-bit installation of Ubuntu maverick (32-bit), I get the following instead:
round(1.0007605, 6) 1.000761
The discrepancy is due to the usual problem of the x87 FPU computing internally with 64-bit precision and then rounding the result to 53-bit precision afterwards, which can give different results from computing directly using 53-bit precision. Mark
participants (2)
-
Kristján Valur Jónsson
-
Mark Dickinson