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
I need to do integer division. I expect the following to return 2 instead of the actual 1:
187 / 100 # => 1
This:
(187.to_f / 100).round # => 2
will work, but does't seem elegant as a solution. Isn't there an integer-only operator that does 187 / 100 = 2?
EDIT
I'll be clearer on my use case since I keep getting down-voted:
I need to calculate taxes on a price. All my prices are in cents. There is nothing below 1 cent in the accountability world so I need to make sure all my prices are integers (those people checking taxes don't like mistakes... really!)
But on the other hand, the tax rate is 19%.
So I wanted to find the best way to write:
def tax_price(price)
price * TAX_RATE / 100
end
that surely returns an integer, without any floating side effect.
I was afraid of going to the floating world because it has very weird side-effects on number representation like:
Ruby strange issue with floating point multiplication
ruby floating point errors
So I found it safer to stay in the integer or the fractional world, hence my question.
You can do it while remaining in the integer world as follows:
def round_div(x,y)
(x + y / 2) / y
end
If you prefer, you could monkey-patch Fixnum with a variant of this:
class Fixnum
def round_div(divisor)
(self + divisor / 2) / divisor
end
end
187.round_div(100) # => 2
No – (a.to_f / b.to_f).round is the canonical way to do it. The behavior of integer / integer is (for example) defined in the C standard as "discarding the remainder" (see http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf page 82) and ruby uses the native C function.
This is a less know method, Numeric#fdiv
You use it like this : 187.fdiv(100).round
Not sure, but this might be what you have in mind.
q, r = 187.divmod(100)
q + (100 > r * 2 ? 0 : 1) # => 2
This should work for you :
Use syntax like this.
(number.to_f/another_number).round
Example:
(18.to_f/5).round
As #MattW already answer (+1), you'd have to cast your integers to floats.
The only other way that is less distracting can be to add .0 to your integer:
(187.0 / 100).round
However, usually we don't operate on concrete integers but variables and this method would be no use.
After some thoughts, I could:
have used BigDecimals but it feels like a bazooka to kill a bird
or I can use a custom method that wouldn't use floating division within the process, as #sawa suggests
def rounded_integer_div(numerator, denominator)
q, r = numerator.divmod(denominator)
q + (100 > r * 2 ? 0 : 1)
end
If what you want is to actually only increase the result by 1 if there's any remainder (e.g. for counting paging/batching), you can use the '%' (modula operation) for remainders checking.
# to add 1 if it's not an even division
a = 187
b = 100
result = a / b #=> 1
result += 1 if (a % b).positive?
#=> 2
# or in one line
result = (a / b) + ((a % b).zero? ? 0 : 1)