12

Appears that the default Python round(1 / 2) gives 0.

How to round float 0.5 up to 1.0, while still rounding 0.45 to 0.0, as the usual school rounding?

NOTE: Sorry for deleting and posting this again, but it was incorrectly marked as duplicate of another question.

Mark Dickinson
  • 27,124
  • 9
  • 79
  • 112
EquipDev
  • 4,643
  • 10
  • 31
  • 58
  • 1
    I know it was incorrectly marked as duplicate, but you may have tagged the person in the comments who dupe hammered the question and clarified the doubt :) – Moinuddin Quadri May 08 '17 at 15:16
  • @MoinuddinQuadri: Thanks for the note, and again sorry for deleting. It just appears that once a higher rated user has tagged it as duplicate, I kind of loose control and interest for the question. However, I still think it is a valid question. – EquipDev May 08 '17 at 15:18
  • 1
    @EquipDev There are many high rate user over here; if one makes a mistake, others are available to fix it ;) And vaultah (one who marked previous question as duplicate) is one of the well known name in Python SO community. He must have misread the question. I am sure if you would have tagged him and described the issue, he would have reopened it :) – Moinuddin Quadri May 08 '17 at 15:21
  • If you disagree with the dupe closure, just explain why. @JacobH who is "that user" you are talking about? You can't see who downvoted the question, period. – vaultah May 08 '17 at 15:21
  • @vaultah : How the question which is dupe hammered here answers the rounding of 0.45 to 0? – Moinuddin Quadri May 08 '17 at 15:22
  • 1
    @vaultah if a the same question is asked within a 2 minute period and the second one is immediately downvoted after the first one was incorrectly marked as a duplicate it doesn't take a rocket surgeon to figure out what is going on. – Jacob H May 08 '17 at 15:30
  • @JacobH and yet you were wrong. – vaultah May 08 '17 at 15:31

3 Answers3

12

It is actually currently considered proper to NOT blindly round *.5 up. Rather, it is proper to round *.5 to the nearest even number. Python 3 implements this "proper" form of "banker rounding", but a lot of other languages don't (yet). Blindly rounding *.5 up produces a slight bias, but "banker rounding" helps to balance it it out. See this thread for more info. So...

Method 1

You could conditionally use aceil(...) function (from the math module for the rounding up aspect. You'll have to do it conditionally in order to also maintain the regular rounding behavior for values less than 0.5. Try something like the following (note that this isn't extremely robust in that it only works on positive values...it should be able to be easily adapted to work with both positive and negative values though):

import math

val = 1.5
x = 0

if (float(val) % 1) >= 0.5:
    x = math.ceil(val)
else:
    x = round(val)

Note that a ceil(...) function will return an integer, not a float. This shouldn't be a major issue, but now you are aware.

Method 2

From the post I linked to above, it looks like another option is to use the decimal module to emulate the "old" way of rounding's behavior. I'm kind of copy & pasting from there, but here you go:

import decimal

x = decimal.Decimal('1.5').quantize(decimal.Decimal('1'), 
rounding=decimal.ROUND_HALF_UP)

Supposedly the decimal.ROUND_HALF_UP form of rounding is what you are looking for. This way you don't have to use a ceil(...) function conditionally.

I'm guessing that this was marked as a duplicate of another because a little digging would have given you more than enough info on this topic. (I didn't mark it as a duplicate, I'm just assuming that is why someone else did.)

Community
  • 1
  • 1
Luke Hollenback
  • 729
  • 6
  • 15
  • @StefanPochmann I edited the first paragraph of this post to explain a little better. It isn't that it always rounds up. Instead, it is that it rounds *.5 to the nearest even number. So yes, `round(1.5) == 2`. But also, `round(2.5) == 2`. – Luke Hollenback May 09 '17 at 16:00
4

Getting the "school" rounding, with rounding away from 0 for value in between, also for negative numbers, the function below can be used. This is also the rounding that was in Python 2.

def round_school(x):
    i, f = divmod(x, 1)
    return int(i + ((f >= 0.5) if (x > 0) else (f > 0.5)))

Some example results:

 1.50:  2
 1.49:  1

 0.50:  1
 0.49:  0

-0.49:  0
-0.50: -1

-1.49: -1
-1.50: -2
EquipDev
  • 4,643
  • 10
  • 31
  • 58
  • 1
    I have found this post more usefull then the accepted post. It seems more clear and you dont have to import any other libs – Vojtech Stas Aug 13 '21 at 14:35
0

Always round off

decimal

from decimal import Decimal, ROUND_HALF_UP


def round(number, ndigits=None):
    """Always round off"""
    exp = Decimal('1.{}'.format(ndigits * '0')) if ndigits else Decimal('1')
    return type(number)(Decimal(number).quantize(exp, ROUND_HALF_UP))


print(round(4.115, 2), type(round(4.115, 2)))
print(round(4.116, 2), type(round(4.116, 2)))
print(round(4.125, 2), type(round(4.125, 2)))
print(round(4.126, 2), type(round(4.126, 2)))
print(round(2.5), type(round(2.5)))
print(round(3.5), type(round(3.5)))
print(round(5), type(round(5)))
print(round(6), type(round(6)))
# 4.12 <class 'float'>
# 4.12 <class 'float'>
# 4.13 <class 'float'>
# 4.13 <class 'float'>
# 3.0 <class 'float'>
# 4.0 <class 'float'>
# 5 <class 'int'>
# 6 <class 'int'>

math

import math


def round(number, ndigits=0):
    """Always round off"""
    exp = number * 10 ** ndigits
    if abs(exp) - abs(math.floor(exp)) < 0.5:
        return type(number)(math.floor(exp) / 10 ** ndigits)
    return type(number)(math.ceil(exp) / 10 ** ndigits)


print(round(4.115, 2), type(round(4.115, 2)))
print(round(4.116, 2), type(round(4.116, 2)))
print(round(4.125, 2), type(round(4.125, 2)))
print(round(4.126, 2), type(round(4.126, 2)))
print(round(2.5), type(round(2.5)))
print(round(3.5), type(round(3.5)))
print(round(5), type(round(5)))
print(round(6), type(round(6)))
# 4.12 <class 'float'>
# 4.12 <class 'float'>
# 4.13 <class 'float'>
# 4.13 <class 'float'>
# 3.0 <class 'float'>
# 4.0 <class 'float'>
# 5 <class 'int'>
# 6 <class 'int'>

Compare

import math
from timeit import timeit
from decimal import Decimal, ROUND_HALF_UP


def round1(number, ndigits=None):
    exp = Decimal('1.{}'.format(ndigits * '0')) if ndigits else Decimal('1')
    return type(number)(Decimal(number).quantize(exp, ROUND_HALF_UP))


def round2(number, ndigits=0):
    exp = number * 10 ** ndigits
    if abs(exp) - abs(math.floor(exp)) < 0.5:
        return type(number)(math.floor(exp) / 10 ** ndigits)
    return type(number)(math.ceil(exp) / 10 ** ndigits)


print(timeit('round1(123456789.1223456789, 5)', globals=globals()))
print(timeit('round2(123456789.1223456789, 5)', globals=globals()))
# 1.9912803000000001
# 1.2140076999999998

The math one is faster.

XerCis
  • 399
  • 2
  • 4