classification
Title: Builtin round function is sometimes inaccurate for floats
Type: behavior Stage: patch review
Components: Interpreter Core Versions: Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: mark.dickinson Nosy List: gboutsioukis, mark.dickinson
Priority: normal Keywords: patch

Created on 2008-01-19 01:04 by mark.dickinson, last changed 2009-06-07 14:37 by mark.dickinson. This issue is now closed.

Files
File name Uploaded Description Edit
round_patch.diff gboutsioukis, 2008-07-19 02:35 patch
issue1869.patch mark.dickinson, 2009-04-17 20:13
issue1869_2.patch mark.dickinson, 2009-04-17 21:51
Messages (16)
msg60129 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2008-01-19 01:04
The documentation for the builtin round(x, n) says:

"Values are rounded to the closest multiple of 10 to the power minus n."

This isn't always true;  for example:

Python 2.6a0 (trunk:59634M, Dec 31 2007, 17:27:56) 
[GCC 4.0.1 (Apple Computer, Inc. build 5370)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> round(562949953421312.5, 1)
562949953421312.62

Here 562949953421312.5 is exactly representable as an IEEE754 double, so the output from the round function should be 
exactly the same as the input.

Assigning this to myself to remind me to try to fix it someday.
msg70000 - (view) Author: George Boutsioukis (gboutsioukis) (Python committer) Date: 2008-07-19 02:35
The issue is that the implementation of round includes multiplying the
number by 10**ndigits, thus unnecessarily losing some precision for
large numbers within the IEEE754 double limits. This means that even
smaller numbers can produce these results for higher ndigit values, eg.

>>> round(56294995342131.5, 3) #one less digit
56294995342131.508

In the submitted patch I used modf to split the number and keep the
integral intact.
msg71279 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2008-08-17 15:30
Thanks for the patch, George!

This patch fixes the problem in some, but not all cases.  I'd like to take 
this a little further.

As far as I can see, getting correctly rounded results in all cases is out 
of reach without either writing lots of multi-precision code (which would 
make the round code unnecessarily complicated and difficult to maintain) 
or using Python's arbitrary-precision integers (which would be slow).

But it would be reasonable to aim for correctly rounded results
msg71281 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2008-08-17 15:35
Hit submit too soon; sorry...

But it would be reasonable to aim for correctly rounded results when the 
second argument to round is not too large (less than 22 in absolute 
value, for example), so that 10**abs(n) is exactly representable as a 
double.  I think this should be easy when n is negative (use fmod to 
compute the remainder, and subtract the remainder from the original 
number to round down, or add (10**-n - remainder) to round up, and a 
little bit more involved when n is positive.

Note that the 3.x version will need to be slightly different, so that it 
does round-half-to-even instead of round-half-away-from-zero.

I think that while these changes could be considered bugfixes they're 
not essential, so should be put off until 2.7/3.1.
msg71286 - (view) Author: George Boutsioukis (gboutsioukis) (Python committer) Date: 2008-08-17 18:05
Hi Mark,

Yes, I see where you are going with this and I agree. I think the py3k
round function is a bit more accurate, any chance this can be backported
to 2.7(after modifying the half-rounding)?

Anyway, I was just playing around with the code when I wrote this patch.
Anything beyond math.h(and only the really portable functions) is
obviously out of the question, so there's little here to work with. 

Despite the (rare) unpredictable results, I think this issue is probably
too minor to spend any more time on;IMO nobody really relies on round()
for accuracy anyway. Perhaps this should be just ignored.
msg80728 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-01-28 21:48
See also issue 4707.
msg80872 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-01-31 15:14
See also issue 5118.
msg83458 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-03-11 08:24
...and issue 5473
msg86088 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-17 20:13
Here's a patch that improves float rounding in a number of respects:

(1) (when available) it uses the recently added correctly-rounded str <-
> float conversions in Python/dtoa.c.  This means that:

- round(x, n) finally does what the docs say it does:  rounds x to the 
closest multiple of 10**-n (and then returns the closest float to the
result).

- for n > 0, the digits of round(x, n) will always agree with those
  of "%.<n>f" % x.

One *really* nice feature is that since e.g. round(x, 2) will always be 
the closest float to a multiple of 0.01, thanks to the recent repr 
changes it'll always be output with at most two places following the 
decimal point, and so will appear to be properly rounded...

Other changes. round(x, n) now:

(2) deals correctly with large values of n

(3) deals correctly with infinities and nans for x

(4) deals correctly with overflow (e.g. round(1.5e308, -308))

(5) doesn't have the weird exception for n = -2**31+1, which was
treated as a single-parameter round.

Still needs tests.  I'm working on them.
msg86089 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-17 20:15
Note that the correctly rounded string<->decimal code only
exists in 3.1, so it won't be possible to backport all this good
stuff to 2.7.
msg86093 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-17 21:51
Updated patch, with tests.
msg86114 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-18 11:51
Fixed in py3k in r71701.

Some of this fix can be backported to 2.7.
msg86115 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-18 12:06
Closing this.  I think 2.7 is fine as it is.
msg86117 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-18 12:27
I take that back.  The 2.7 round still has some problems.
Here's one example:

>>> x = 5e15 + 1  # exactly representable as an IEEE 754 double
>>> x
5000000000000001.0
>>> round(x)
5000000000000002.0

Another nit:

>>> round(-0.0, 0)  # should retain the sign of zero
0.0

Reopening.
msg86119 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-04-18 15:06
The two problems mentioned above have now been fixed in r71715.

There are (at least) two remaining problems with the round function in 2.x 
that I'm not going to fix, because the added complication doesn't seem
worth it.

(1) round(x, n) doesn't give correct results with large values of n;
    but small values (e.g., abs(n) <= 50) should be fine unless x is
    huge.

(2) round(x, n) still occasionally rounds the wrong way in borderline
    cases, even for small n.

Closing (again!)
msg89041 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2009-06-07 14:37
See also issue 6228.
History
Date User Action Args
2009-06-07 14:37:32mark.dickinsonsetmessages: + msg89041
2009-04-18 15:06:24mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg86119
2009-04-18 12:27:24mark.dickinsonsetstatus: closed -> open
resolution: fixed -> (no value)
messages: + msg86117
2009-04-18 12:06:38mark.dickinsonsetstatus: open -> closed
resolution: fixed
messages: + msg86115
2009-04-18 11:51:18mark.dickinsonsetmessages: + msg86114
versions: - Python 3.1
2009-04-17 21:52:18mark.dickinsonsetcomponents: + Interpreter Core
2009-04-17 21:51:57mark.dickinsonsetfiles: + issue1869_2.patch

messages: + msg86093
stage: test needed -> patch review
2009-04-17 20:15:17mark.dickinsonsetmessages: + msg86089
2009-04-17 20:13:54mark.dickinsonsetfiles: + issue1869.patch

messages: + msg86088
stage: test needed
2009-03-11 08:30:12mark.dickinsonlinkissue5473 superseder
2009-03-11 08:24:52mark.dickinsonsetmessages: + msg83458
2009-01-31 15:14:18mark.dickinsonsetmessages: + msg80872
2009-01-28 21:48:08mark.dickinsonsetpriority: low -> normal
messages: + msg80728
2008-08-17 18:05:20gboutsioukissetmessages: + msg71286
2008-08-17 15:35:35mark.dickinsonsetmessages: + msg71281
versions: - Python 3.0
2008-08-17 15:30:22mark.dickinsonsetmessages: + msg71279
versions: + Python 3.1, Python 2.7, - Python 2.6, Python 2.5
2008-07-19 02:35:25gboutsioukissetfiles: + round_patch.diff
keywords: + patch
messages: + msg70000
nosy: + gboutsioukis
2008-01-19 01:04:25mark.dickinsoncreate