4

Has the built in round() function in Python changed between 2.4 and 2.7?

Python 2.4:

Python 2.4.6 (#1, Feb 12 2009, 14:52:44)
[GCC 3.4.6 20060404 (Red Hat 3.4.6-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f = 1480.39499999999998181010596454143524169921875
>>> round(f,2)
1480.4000000000001
>>>

Python 2.7:

Python 2.7.1 (r271:86832, May 13 2011, 08:14:41)
[GCC 3.4.6 20060404 (Red Hat 3.4.6-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f = 1480.39499999999998181010596454143524169921875
>>> round(f, 2)
1480.39
>>>

Is there anyway to get the Python 2.4 behaviour back?

I'm aware that the right answer is of course to use the decimal arithmetic module. Unfortunately, this probably isn't an option at this point given time limitations.

Update

For context i'm comparing values in two systems, one of which uses a decimal representation and the other a floating point one. This may (or may not) be a legitimate difference between the systems which needs to be checked further, so i'll consult users and deal with it at the "reporting" level, rather than at the point I get the data from the systems.

Thanks for your help!

Paul Johnson
  • 1,329
  • 1
  • 12
  • 25
  • You're providing far more digits than the internal representation can handle - it's possible that the difference lies in the interpretation of the input, not the `round`. – Mark Ransom Dec 20 '11 at 22:43
  • 3
    P.S. The Python 2.7 answer seems more correct to me, I wouldn't expect to find an option to deliver incorrect results. – Mark Ransom Dec 20 '11 at 22:48
  • 1
    `Unfortunately, this [decimal module] probably isn't an option at this point given time limitations.` I doubt that that is really true, at the very least, you shouldn't expect a float to give precise result or precise rounding as float are imprecise by definition. Save yourself the hassle and convert your program to decimal. – Lie Ryan Dec 20 '11 at 22:55
  • @Mark: No, he's not, he's just writing the *exact* value of `1480.395`. – dan04 Dec 20 '11 at 22:57
  • @dan04: There is no such thing as "exact value of 1480.395" in (binary) floating point. If he really wants that *exact* value, he *must* use Decimal, or equivalent (gmpy, or roll his own using integers, etc.). – John Y Dec 20 '11 at 23:03
  • 1
    @JohnY, I think what @dan04 is trying to say is that the very long decimal number is in fact an exact binary number, and it's the binary number closest to `1480.395`. Doesn't invalidate my speculation that the difference is caused by a change in input processing, although other evidence points elsewhere. – Mark Ransom Dec 20 '11 at 23:13
  • @Mark: I don't know what you mean by "input processing" or "other evidence". The Python devs (mostly Mark Dickinson) did some pretty extensive reworking of float behavior in Python 2.7/3.1, specifically improving `repr` and fixing `round` ("fix" here meaning relative to the IEEE floating-point specification). – John Y Dec 20 '11 at 23:24
  • @JohnY, the release notes for 2.7/3.1 are the evidence of which I speak. I just wanted to point out a possibility not in the original question, that the conversion of a string to a binary floating point number had changed (which is what I mean by "input processing"). Your answer already has my +1. – Mark Ransom Dec 20 '11 at 23:38
  • @MarkRandom: Yes, that's what I meant. – dan04 Dec 21 '11 at 04:35

4 Answers4

6

The answer to your first question is: yes, round was fixed in Python 2.7.

The answer to your second question is: I'm not sure, short of actually using an earlier Python (2.6 should still exhibit 2.4's behavior). In this particular case, your value is indistinguishable from 1480.395, and 1480.395 (to us base-10 humans) rounds to 1480.40. So I suppose you could try first rounding to one place beyond what you are really after, turn that into a string, then get a Decimal from that... but sorry, I can't think of anything that doesn't involve Decimal. If I think of something (before someone else posts something better), I'll come back and edit.

(But really, is Decimal all that hard to use?)

Edit: Here is an example of how to use Decimal:

>>> from decimal import Decimal
>>> f = Decimal('1480.395')
>>> f.quantize(Decimal('0.00'))
Decimal('1480.40')
>>> float(Decimal('1480.40'))
1480.4

Some notes:

Python 2.7 allows you to use floats as input to Decimal. Don't do this as you would be back where you started. Also, don't use the built-in round function on a Decimal, because that function is going to convert your Decimal right back to float, and thus you would be back where you started. In its simplest form, Decimal's quantize takes another Decimal that has the number of decimal places you really want (think of it as a template for the rounding). If your data absolutely has to be float, then only convert back to float after all your Decimal calculations are done.

Finally: I'm not sure this covers all the possible weird corner cases in Python 2.4's float behavior. If you are relying on exactly Python 2.4's behavior, there may be no substitute to running on Python 2.4. What I have described above is merely one step closer to human-style rounding.

John Y
  • 14,123
  • 2
  • 48
  • 72
  • Since the closest binary representation of 1480.395 is just a hair under, rounding to any number of digits 3 or larger just leaves you where you started - double rounding doesn't help. – Mark Ransom Dec 20 '11 at 23:15
  • 1
    @Mark: No, I'm not suggesting to round on the float; I'm saying round the *Decimal* (using `quantize`). I'll clarify in the answer... – John Y Dec 20 '11 at 23:17
3

Probably not since Python 2.4 actually returned an incorrect result. Since you are rounding away an amount that is strictly less than 5, the correct result should be rounded down.

If you can describe the exact behaviour you need, we may be able to create an alternative approach.

casevh
  • 11,093
  • 1
  • 24
  • 35
1

In the what's new in python 2.6 page I see the following (with reference to PEP 3141):

Python 3.0 adds several abstract base classes for numeric types inspired by Scheme’s numeric tower. These classes were backported to 2.6 as the numbers module.

...

In Python 3.0, the PEP slightly redefines the existing builtins round(), math.floor(), math.ceil(), and adds a new one, math.trunc(), that’s been backported to Python 2.6. math.trunc() rounds toward zero, returning the closest Integral that’s between the function’s argument and zero.

And, thanks to @mark-dickinson comment, in the what's new in python 2.7 page, the following text is found:

The round() function is also now correctly rounded.

So yes, the change happened in python 2.7. This can be verified checking that in python 2.6 the behaviour is still the old one:

Python 2.6.7 (r267:88850, Aug 11 2011, 12:18:09) 
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f = 1480.39499999999998181010596454143524169921875
>>> round(f, 2)
1480.4000000000001

In my linux distribution I still have access to both python 2.6 and python 2.7 and probably you have also in yours; so, if you want the old behaviour, maybe you can try to run the code in python 2.6.

jcollado
  • 39,419
  • 8
  • 102
  • 133
  • The relevant change was actually in Python 2.7, not Python 2.6, though it only gets a brief mention in the 2.7 'what's new' document: "The round() function is also now correctly rounded." – Mark Dickinson Dec 22 '11 at 18:52
  • @MarkDickinson Thanks, I've udpated the answer with the information you've provided. – jcollado Dec 22 '11 at 19:44
0

Maybe I missed this in other replies, but in fact the round function of the last Python 2.# version (version 2.7.18) is also not 'correct', i.e. not according to the IEC 60559 standard. Try this:

print(round(2.25, 1))

And you should get 2.2 and not 2.3. This was fixed in the 3.# versions (I tested 3.5 onwards only).

David.
  • 132
  • 6
  • Well, the standard specifies 5 different rounding modes, without saying that one of them is "correct" and the others are "wrong". Python 2 uses the "to nearest, ties away from zero" mode. This is what most of us were taught in school. [Python 3 adopts the "to nearest, ties to even" mode](https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior). When people say rounding was "corrected" in Python 2.7, they mean that previously, Python didn't even consistently follow its intended mode, as the OP's example shows. – John Y Jul 08 '21 at 17:31
  • Thanks. But now you make it sound as if the different procedures for rounding are equally valid and that the main concern of the end user is that Python follows any of those procedures. I would argue that the procedures are not equally valid and that the main concern is to have the best available procedure. It is being recommended to round to even so as to avoid systematic bias. In other words: so as to avoid being wrong so often. – David. Jul 10 '21 at 04:52
  • Presumably, the standard wouldn't have defined various rounding modes unless they each served some purpose. The designers of Python 2 and many other languages specifically chose to implement half-away-from-zero rounding because they felt it best satisfied the principle of least surprise, especially for a built-in (as opposed to a math or scientific library function). That is a higher concern for many users than avoiding what they might consider a very small or even unnoticeable amount of bias. – John Y Jul 14 '21 at 19:36