I am facing a strange behavior of the round() function:
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", round(n))
This code prints:
0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
I expected the floating values to be always rounded up, but instead, it is rounded to the nearest even number.
Why such behavior, and what is the best way to get the correct result?
I tried to use the fractions but the result is the same.
The Numeric Types section documents this behaviour explicitly:
round(x[, n])
x rounded to n digits, rounding half to even. If n is omitted, it defaults to 0.
Note the rounding half to even. This is also called bankers rounding; instead of always rounding up or down (compounding rounding errors), by rounding to the nearest even number you average out rounding errors.
If you need more control over the rounding behaviour, use the decimal module, which lets you specify exactly what rounding strategy should be used.
For example, to round up from half:
>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
... ctx.rounding = ROUND_HALF_UP
... for i in range(1, 15, 2):
... n = Decimal(i) / 2
... print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7
For example:
from decimal import Decimal, ROUND_HALF_UP
Decimal(1.5).quantize(0, ROUND_HALF_UP)
# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)
You can use this:
import math
def normal_round(n):
if n - math.floor(n) < 0.5:
return math.floor(n)
return math.ceil(n)
It will round number up or down properly.
round() will round either up or down, depending on if the number is even or odd. A simple way to only round up is:
int(num + 0.5)
If you want this to work properly for negative numbers use:
((num > 0) - (num < 0)) * int(abs(num) + 0.5)
Note, this can mess up for large numbers or really precise numbers like 5000000000000001.0 and 0.49999999999999994.
Love the fedor2612 answer. I expanded it with an optional "decimals" argument for those who want to use this function to round any number of decimals (say for example if you want to round a currency $26.455 to $26.46).
import math
def normal_round(n, decimals=0):
expoN = n * 10 ** decimals
if abs(expoN) - abs(math.floor(expoN)) < 0.5:
return math.floor(expoN) / 10 ** decimals
return math.ceil(expoN) / 10 ** decimals
oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)
print(oldRounding)
print(newRounding)
Output:
26.45
26.46
The behavior you are seeing is typical IEEE 754 rounding behavior. If it has to choose between two numbers that are equally different from the input, it always picks the even one. The advantage of this behavior is that the average rounding effect is zero - equally many numbers round up and down. If you round the half way numbers in a consistent direction the rounding will affect the expected value.
The behavior you are seeing is correct if the objective is fair rounding, but that is not always what is needed.
One trick to get the type of rounding you want is to add 0.5 and then take the floor. For example, adding 0.5 to 2.5 gives 3, with floor 3.
Why make it so complicated? (Only works for positive numbers)
def HalfRoundUp(value):
return int(value + 0.5)
You could of course make it into a lambda which would be:
HalfRoundUp = lambda value: int(value + 0.5)
Unfortunately, this simple answer doesn't work with negative numbers, but it can be fixed with the floor function from math: (This works for both positive and negative numbers too)
from math import floor
def HalfRoundUp(value):
floor(value + 0.5)
Short version: use the decimal module. It can represent numbers like 2.675 precisely, unlike Python floats where 2.675 is really 2.67499999999999982236431605997495353221893310546875 (exactly). And you can specify the rounding you desire: ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP, and ROUND_05UP are all options.
In the question this is basically an issue when dividing a positive integer by 2. The easisest way is int(n + 0.5) for individual numbers.
However we cannot apply this to series, therefore what we then can do for example for a pandas dataframe, and without going into loops, is:
import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))
A small addition as the rounding half up with some of the solutions might not work as expected in some cases.
Using the function from above for instance:
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
round_half_up(1.35, 1)
1.4
round_half_up(4.35, 1)
4.3
Where I was expecting 4.4. What did the trick for me was converting x into a string first.
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(str(x)).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
round_half_up(4.35, 1)
4.4
Rounding to the nearest even number has become common practice in numerical disciplines. "Rounding up" produces a slight bias towards larger results.
So, from the perspective of the scientific establishment, round has the correct behavior.
Here is another solution.
It will work as normal rounding in excel.
from decimal import Decimal, getcontext, ROUND_HALF_UP
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
def c_round(x, digits, precision=5):
tmp = round(Decimal(x), precision)
return float(tmp.__round__(digits))
c_round(0.15, 1) -> 0.2, c_round(0.5, 0) -> 1
The following solution achieved "school fashion rounding" without using the decimal module (which turns out to be slow).
def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
if (a_in * 10 ** (n_in + 1)) % 10 == 5:
return round(a_in + 1 / 10 ** (n_in + 1), n_in)
else:
return round(a_in, n_in)
e.g.
print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01
So just to make sure there is a crystal clear working example here, I wrote a small convenience function
def round_half_up(x: float, num_decimals: int) -> float:
"""Use explicit ROUND HALF UP. See references, for an explanation.
This is the proper way to round, as taught in school.
Args:
x:
num_decimals:
Returns:
https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers-in-python
"""
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
And an appropriate set of test cases
def test_round_half_up():
x = 1.5
y = round_half_up(x, 0)
assert y == 2.0
y = round_half_up(x, 1)
assert y == 1.5
x = 1.25
y = round_half_up(x, 1)
assert y == 1.3
y = round_half_up(x, 2)
assert y == 1.25
This is a function that takes the number of decimal places as an argument.
It also rounds up half decimal.
import math
def normal_round(n, decimal_places):
if int((str(n)[-1])) < 5:
return round(n, decimal_places)
return round(n + 10**(-1 * (decimal_places+1)), decimal_places)
Test cases:
>>> normal_round(5.12465, 4)
5.1247
>>> normal_round(5.12464, 4)
5.1246
>>> normal_round(5.12467, 4)
5.1247
>>> normal_round(5.12463, 4)
5.1246
>>> normal_round(5.1241, 4)
5.1241
>>> normal_round(5.1248, 4)
5.1248
>>> normal_round(5.1248, 3)
5.125
>>> normal_round(5.1242, 3)
5.124
You can use:
from decimal import Decimal, ROUND_HALF_UP
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
A classical mathematical rounding without any libraries
def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
c += 1
return c/m
Compare:
print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1)) # 0 1 0 2 2 0.1
print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1)) # 0 1 1 2 3 0.2
Knowing that round(9.99,0) rounds to int=10 and int(9.99) rounds to int=9 brings success:
Goal: Provide lower and higher round number depending on value
def get_half_round_numers(self, value):
"""
Returns dict with upper_half_rn and lower_half_rn
:param value:
:return:
"""
hrns = {}
if not isinstance(value, float):
print("Error>Input is not a float. None return.")
return None
value = round(value,2)
whole = int(value) # Rounds 9.99 to 9
remainder = (value - whole) * 100
if remainder >= 51:
hrns['upper_half_rn'] = round(round(value,0),2) # Rounds 9.99 to 10
hrns['lower_half_rn'] = round(round(value,0) - 0.5,2)
else:
hrns['lower_half_rn'] = round(int(value),2)
hrns['upper_half_rn'] = round(int(value) + 0.5,2)
return hrns
Some testing:
yw
import math
# round tossing n digits from the end
def my_round(n, toss=1):
def normal_round(n):
if isinstance(n, int):
return n
intn, dec = str(n).split(".")
if int(dec[-1]) >= 5:
if len(dec) == 1:
return math.ceil(n)
else:
return float(intn + "." + str(int(dec[:-1]) + 1))
else:
return float(intn + "." + dec[:-1])
while toss >= 1:
n = normal_round(n)
toss -= 1
return n
for n in [1.25, 7.3576, 30.56]:
print(my_round(n, 2))
1.0
7.36
31
import math
def round_half_up(x: float) -> int:
if x < 0:
return math.trunc(x) if -x % 1 < 0.5 else math.floor(x)
else:
return math.trunc(x) if x % 1 < 0.5 else math.ceil(x)
This even works for corner cases like 0.49999999999999994 and 5000000000000001.0.
You can try this
def round(num):
return round(num + 10**(-9))
it will work since num = x.5 will always will be x.5 + 0.00...01 in the process which its closer to x+1 hence the round function will work properly and it will round x.5 to x+1
In ruby, some large numbers are larger than infinity. Through binary search, I discovered:
(1.0/0) > 10**9942066.000000001 # => false
(1.0/0) > 10**9942066 # => true
RUBY_VERSION # => "2.3.0"
Why is this? What is special about 109942066? It doesn't seem to be an arbitrary number like 9999999, it is not close to any power of two (it's approximately equivelent to 233026828.36662442).
Why isn't ruby's infinity infinite? How is 109942066 involved?
I now realize, any number greater than 109942066 will overflow to infinity:
10**9942066.000000001 #=> Infinity
10**9942067 #=> Infinity
But that still leaves the question: Why 109942066?
TL;DR
I did the calculations done inside numeric.c's int_pow manually, checking where an integer overflow (and a propagation to Bignum's, including a call to rb_big_pow) occurs. Once the call to rb_big_pow happens there is a check whether the two intermediate values you've got in int_pow are too large or not, and the cutoff value seems to be just around 9942066 (if you're using a base of 10 for the power). Approximately this value is close to
BIGLEN_LIMIT / ceil(log2(base^n)) * n ==
32*1024*1024 / ceil(log2(10^16)) * 16 ==
32*1024*1024 / 54 * 16 ~=
9942054
where BIGLEN_LIMIT is an internal limit in ruby which is used as a constant to check if a power calculation would be too big or not, and is defined as 32*1024*1024. base is 10, and n is the largest power-of-2 exponent for the base that would still fit inside a Fixnum.
Unfortunately I can't find a better way than this approximation, due to the algorithm used to calculate powers of big numbers, but it might be good enough to use as an upper limit if your code needs to check validity before doing exponentiation on big numbers.
Original question:
The problem is not with 9942066, but that with one of your number being an integer, the other one being a float. So
(10**9942066).class # => Bignum
(10**9942066.00000001).class # => Float
The first one is representable by a specific number internally, which is smaller than Infinity. The second one, as it's still a float is not representable by an actual number, and is simply replaced by Infinity, which is of course not larger than Infinity.
Updated question:
You are right that there seem to be some difference around 9942066 (if you're using a 64-bit ruby under Linux, as the limits might be different under other systems). While ruby does use the GMP library to handle big numbers, it does some precheck before even going to GMP, as shown by the warnings you can receive. It will also do the exponentiation manually using GMP's mul commands, without calling GMP's pow functions.
Fortunately the warnings are easy to catch:
irb(main):010:0> (10**9942066).class
=> Bignum
irb(main):005:0> (10**9942067).class
(irb):5: warning: in a**b, b may be too big
=> Float
And then you can actually check where these warnings are emitted inside ruby's bignum.c library.
But first we need to get to the Bignum realm, as both of our numbers are simple Fixnums. The initial part of the calculation, and the "upgrade" from fixnum to bignum is done inside numeric.c. Ruby does quick exponentiation, and at every step it checks whether the result would still fit into a Fixnum (which is 2 bits less than the system bitsize: 62 bits on a 64 bit machine). If not, it will then convert the values to the Bignum realm, and continues the calculations there. We are interested at the point where this conversion happens, so let's try to figure out when it does in our 10^9942066 example (I'm using x,y,z variables as present inside the ruby's numeric.c code):
x = 10^1 z = 10^0 y = 9942066
x = 10^2 z = 10^0 y = 4971033
x = 10^2 z = 10^2 y = 4971032
x = 10^4 z = 10^2 y = 2485516
x = 10^8 z = 10^2 y = 1242758
x = 10^16 z = 10^2 y = 621379
x = 10^16 z = 10^18 y = 621378
x = OWFL
At this point x will overflow (10^32 > 2^62-1), so the process will continue on the Bignum realm by calculating x**y, which is (10^16)^621378 (which are actually still both Fixnums at this stage)
If you now go back to bignum.c and check how it determines if a number is too large or not, you can see that it will check the number of bits required to hold x, and multiply this number with y. If the result is larger than 32*1024*1024, it will then fail (emit a warning and does the calculations using basic floats).
(10^16) is 54 bits (ceil(log_2(10^16)) == 54), 54*621378 is 33554412. This is only slightly smaller than 33554432 (by 20), the limit after which ruby will not do Bignum exponentiation, but simply convert y to double, and hope for the best (which will obviously fail, and just return Infinity)
Now let's try to check this with 9942067:
x = 10^1 z = 10^0 y = 9942067
x = 10^1 z = 10^1 y = 9942066
x = 10^2 z = 10^1 y = 4971033
x = 10^2 z = 10^3 y = 4971032
x = 10^4 z = 10^3 y = 2485516
x = 10^8 z = 10^3 y = 1242758
x = 10^16 z = 10^3 y = 621379
x = 10^16 z = OWFL
Here, at the point z overflows (10^19 > 2^62-1), the calculation will continue on the Bignum realm, and will calculate x**y. Note that here it will calculate (10^16)^621379, and while (10^16) is still 54 bits, 54*621379 is 33554466, which is larger than 33554432 (by 34). As it's larger you'll get the warning, and ruby will only to calculations using double, hence the result is Infinity.
Note that these checks are only done if you are using the power function. That's why you can still do (10**9942066)*10, as similar checks are not present when doing plain multiplication, meaning you could implement your own quick exponentiation method in ruby, in which case it will still work with larger values, although you won't have this safety check anymore. See for example this quick implementation:
def unbounded_pow(x,n)
if n < 0
x = 1.0 / x
n = -n
end
return 1 if n == 0
y = 1
while n > 1
if n.even?
x = x*x
n = n/2
else
y = x*y
x = x*x
n = (n-1)/2
end
end
x*y
end
puts (10**9942066) == (unbounded_pow(10,9942066)) # => true
puts (10**9942067) == (unbounded_pow(10,9942067)) # => false
puts ((10**9942066)*10) == (unbounded_pow(10,9942067)) # => true
But how would I know the cutoff for a specific base?
My math is not exactly great, but I can tell a way to approximate where the cutoff value will be. If you check the above calls you can see the conversion between Fixnum and Bignum happens when the intermediate base reaches the limit of Fixnum. The intermediate base at this stage will always have an exponent which is a power of 2, so you just have to maximize this value. For example let's try to figure out the maximum cutoff value for 12.
First we have to check what is the highest base we can store in a Fixnum:
ceil(log2(12^1)) = 4
ceil(log2(12^2)) = 8
ceil(log2(12^4)) = 15
ceil(log2(12^8)) = 29
ceil(log2(12^16)) = 58
ceil(log2(12^32)) = 115
We can see 12^16 is the max we can store in 62 bits, or if we're using a 32 bit machine 12^8 will fit into 30 bits (ruby's Fixnums can store values up to two bits less than the machine size limit).
For 12^16 we can easily determine the cutoff value. It will be 32*1024*1024 / ceil(log2(12^16)), which is 33554432 / 58 ~= 578525. We can easily check this in ruby now:
irb(main):004:0> ((12**16)**578525).class
=> Bignum
irb(main):005:0> ((12**16)**578526).class
(irb):5: warning: in a**b, b may be too big
=> Float
Now we hate to go back to our original base of 12. There the cutoff will be around 578525*16 (16 being the exponent of the new base), which is 9256400. If you check in ruby, the values are actually quite close to this number:
irb(main):009:0> (12**9256401).class
=> Bignum
irb(main):010:0> (12**9256402).class
(irb):10: warning: in a**b, b may be too big
=> Float
Note that the problem is not with the number but with the operation, as told by the warning you get.
$ ruby -e 'puts (1.0/0) > 10**9942067'
-e:1: warning: in a**b, b may be too big
false
The problem is 10**9942067 breaks Ruby's power function. Instead of throwing an exception, which would be a better behavior, it erroneously results in infinity.
$ ruby -e 'puts 10**9942067'
-e:1: warning: in a**b, b may be too big
Infinity
The other answer explains why this happens near 10e9942067.
10**9942067 is not greater than infinity, it is erroneously resulting in infinity. This is a bad habit of a lot of math libraries that makes mathematicians claw their eyeballs out in frustration.
Infinity is not greater than infinity, they're equal, so your greater than check is false. You can see this by checking if they're equal.
$ ruby -e 'puts (1.0/0) == 10**9942067'
-e:1: warning: in a**b, b may be too big
true
Contrast this with specifying the number directly using scientific notation. Now Ruby doesn't have to do math on huge numbers, it just knows that any real number is less than infinity.
$ ruby -e 'puts (1.0/0) > 10e9942067'
false
Now you can put on as big an exponent as you like.
$ ruby -e 'puts (1.0/0) > 10e994206700000000000000000000000000000000'
false