How would you safely round a floating point number to the nearest integer ? Python has the built in function round , but it returns a floating point number

>>> round(4.9)
5.0
>>> help(round)
Help on built-in function round in module __builtin__:

round(...)
    round(number[, ndigits]) -> floating point number
    
    Round a number to a given precision in decimal digits (default 0 digits).
    This always returns a floating point number.  Precision may be negative.

Using int(round(x)) to obtain the nearest integer looks like a bad idea because the int() function is discontinuous near integer values, and therefore, round(x) returns a value wich is very close to a point of discontinuity of int :

>>> int(4.999999999999999)
4
>>> int(4.99999999999999999999)
5

I designed the following function, which is guaranteed to be stable (except near half integers, the natural points of discontinuity of the function 'nearest integer'):

def iround(x):
    """iround(number) -> integer
    Round a number to the nearest integer."""
    return int(round(x) - .5) + (x > 0)

Here are a few calls

>>> iround(4.9)
5
>>> iround(5.1)
5
>>> iround(-4.9)
-5
>>> iround(-5.1)
-5

Now, it turns out that for every value that I tried on my computer, iround returns exactly the same value as int(round(x)) . To see it I wrote a function for the difference

def diff(x):
    return int(round(x)) - iround(x)

My question is: does the diff function above always return 0 for every floating value of x on any computer, and is there something in the implementation of the builtin round function which guarantees stability for int(round(x)) ? This does not seem to be documented in the official doc. What do you think of this ?

Edited 6 Years Ago by Gribouillis: n/a

Sorry, there was a bug in the iround function above. It should be

def iround(x):
    """iround(number) -> integer
    Round a number to the nearest integer."""
    y = round(x) - .5
    return int(y) + (y > 0)

The previous version does not work near 0.

Perhaps I am missing something, but the following code should work. But once you get past the precision of floating point numbers you will have to use the decimal module whichever way you do it.

import decimal
x = decimal.Decimal("2.4999999999999999999999999")
whole, remain = divmod(x, 1)
if remain >= decimal.Decimal("0.5"):
    whole += 1
print whole

Edited 6 Years Ago by woooee: Can't compare a decimal and a float

Perhaps I am missing something, but the following code should work. But once you get past the precision of floating point numbers you will have to use the decimal module whichever way you do it.

import decimal
x = decimal.Decimal("2.4999999999999999999999999")
whole, remain = divmod(x, 1)
if remain >= decimal.Decimal("0.5"):
    whole += 1
print whole

Yes, I think you are definitely missing something. The question is not to go past the precision of floating point numbers, the question is stability and portability. For example, if I want the integer nearest to 3.1, I don't need much precision. The result is 3 and it is a stable and portable result. Stable means that if I change 3.1 by adding or substracting a small perturbation, say 0.0001, the result is still 3 (unstability should occur only near half integers, like 3.5). Portable means that the result should be 3 on any computer.

Now, the expression int(round(3.1)) could be unstable or non portable, because round(3.1) returns the floating number 3.0 and usually, floating point numbers are not exact in machine representation (but note that integers like 3.0 can possibly be represented exactly). The int function is unstable near the point 3.0 because int(3.0 - 1e-10) == 2 and int(3.0 + 1e-10) == 3 . So I cannot guarantee that int(round(3.1)) yields 3 in any circumstances, and not 2 on certain computers or OSes. As far as I know, this is not documented in python.

Using decimal numbers here is not a good idea because it will be much slower than using ordinary floating point numbers and the problem does not require more precision than the usual float precision.

Edited 6 Years Ago by Gribouillis: n/a

I might also be missing something but this should work:

def round(num):
	if (num > 0):
		return int(num+.5)
	else:
		return int(num-.5)

Edited 6 Years Ago by nickles: n/a

Comments
excelent

My question is: does the diff function above always return 0 for every floating value of x on any computer, and is there something in the implementation of the builtin round function which guarantees stability for int(round(x)) ? This does not seem to be documented in the official doc. What do you think of this ?

If it's not documented in the official documentation, use your function and forsake int+round. In fact, if you're not sure, or you think somebody reading your code later might not be sure, just go ahead and use your function. What's the harm? Performance? You're using Python.

I also think that the action of using int function or as hidden by round, is disruptive as it is very discontinuous by its very nature. Sometimes it is better to stay with integers and be clearly discontinuous. Same could be done, maybe by multiplying numbers by some constant before use and dividing by the factor in the end even with floating point numbers.

Where the need usually arise is for printing neat results. Is there int embedded in numeric algorithm inside or is it only on this interface "surface"? If it is inside, maybe there could be some kind of sanity check filter for disruptive "imposible" data points and do recalculation to slightly tweaked parameter values to get them into the general line. There is though the real chaotic nature of things and we should not wipe it under the carpet.

Edited 6 Years Ago by pyTony: n/a

Thank you for all your answers. It seems that the more secure way is to use an auxiliary function. I'll stay with nickles' solution, or the iround function I wrote above.
There once was a math.rint function (in python 1.6), but it was removed by G Van Rossum:

After a brief consult with Tim, I've decided to drop math.rint() -- it's not standard C, can't be implemented in portable C, and its naive (non-IEEE-754-aware) effect can easily be had in other ways.

See this link http://markmail.org/message/4di24iqm7zhc4rwc.

Edited 6 Years Ago by Gribouillis: n/a

This question has already been answered. Start a new discussion instead.