What you describe is (almost) the round half up strategy. However using int
it won't work for negative numbers:
>>> def round_half_up(x, n=0):
... shift = 10 ** n
... return int(x*shift + 0.5) / shift
...
>>> round_half_up(-1.26, 1)
-1.2
Instead you should use math.floor
in order to handle negative number correctly:
>>> import math
>>>
>>> def round_half_up(x, n=0):
... shift = 10 ** n
... return math.floor(x*shift + 0.5) / shift
...
>>> round_half_up(-1.26, 1)
-1.3
This strategy suffers from the effect that it tends to distort statistics of a collection of numbers, such as the mean or the standard deviation. Suppose you have collected some numbers and all of them end in .5
; then rounding each of them up will clearly increase the average:
>>> numbers = [-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5]
>>> N = len(numbers)
>>> sum(numbers) / N
0.0
>>> sum(round_half_up(x) for x in numbers) / N
0.5
If we use the strategy round half to even instead this will cause some numbers to be rounded up and others to be rounded down and hence compensating each other:
>>> sum(round(x) for x in numbers) / N
0.0
As you can see, for the example, the average remains preserved.
This works of course only if the numbers are uniformly distributed. If there is a tendency to favor numbers of the form odd + 0.5
then this strategy won't prevent a bias either:
>>> numbers = [i + 0.5 for i in range(-3, 3, 2)]
>>> N = len(numbers)
>>> sum(numbers) / N
-0.5
>>> sum(round_half_up(x) for x in numbers) / N
0.0
>>> sum(round(x) for x in numbers) / N
0.0
For this set of numbers, round
is effectively doing "round half up" so both methods suffer from the same bias.
As you can see, the rounding strategy clearly influences the bias of several statistics such as the average. "round half to even" tends to remove that bias but obviously favors even over odd numbers and thus also distorts the original distribution.
A note on float
objects
Due to limited floating point precision this "round half up" algorithm might also yield some unexpected surprises:
>>> round_half_up(-1.225, 2)
-1.23
Interpreting -1.225
as a decimal number we would expect the result to be -1.22
instead. We get -1.23
because the intermediate floating point number in round_half_up
slips a bit over it's expected value:
>>> f'{-1.225 * 100 + 0.5:.20f}'
'-122.00000000000001421085'
floor
'ing that numbers gives us -123
(instead of -122
if we had gotten -122.0
before). That's due to floating point error and starts with the fact that -1.225
is actually not stored as -1.225
in memory but as a number which is a little bit smaller. For that reason using Decimal
is the only way to get correct rounding in all cases.