How to properly round-up half float numbers?

84,986

Solution 1

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

Solution 2

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)

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
84,986

Related videos on Youtube

Delgan
Author by

Delgan

It's me.

Updated on July 17, 2022

Comments

  • Delgan
    Delgan almost 2 years

    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.

    • yurib
      yurib over 8 years
      can't explain the behaviour of round() but you could use math.ceil() if you always want to round up
    • Delgan
      Delgan over 8 years
      @yurib I would like 1.3 to be rounded down to 1, so I can not use ceil().
    • yurib
      yurib over 8 years
    • StoryTeller - Unslander Monica
      StoryTeller - Unslander Monica over 8 years
      Many days have passed since I studied error analysis. However If I recall correctly, the rounding of 5*10**-k depends on the digit preceding it. By rounding up for uneven digits and down for even digits, you get a positive error half the time and an even error half the time (in theory). When you perform many additions, those errors can cancel each-other
  • Robert E
    Robert E over 8 years
    IEEE 754 rounding half to even is also described at en.wikipedia.org/wiki/Rounding#Round_half_to_even
  • Mark Dickinson
    Mark Dickinson about 6 years
    There are some subtleties that aren't addressed by this solution. E.g., what result does this give if num = -2.4? What about num = 0.49999999999999994? num = 5000000000000001.0? On a typical machine using IEEE 754 format and semantics, this solution gives the wrong answer for all three of these cases.
  • Matthew D. Scholefield
    Matthew D. Scholefield about 6 years
    @Mark Dickinson I've updated the post to mention this. Thanks
  • dhobbs
    dhobbs about 5 years
    In your example, is there a benefit to modifying the local context as opposed to just using the rounding argument as in: n.to_integral_value(rounding=ROUND_HALF_UP)?
  • Martijn Pieters
    Martijn Pieters about 5 years
    @dhobbs: setting the context once is clearer in intent, but from a technical point of view there is no difference.
  • ShadowRanger
    ShadowRanger over 4 years
    Now x.499999999 will be subject to half-even rounding, and will (half the time, assuming floating point precision issues don't force it one way or the other) get rounded up. That's worse than the initial scenario, as you're now rounding to the more distant number.
  • lalala
    lalala over 3 years
    Time and time I am shocked there is not internal function like this. I mean, it is not like people nowadays use a lot of numpy and math in python to implement numerical algorithms....
  • kol
    kol about 3 years
    Sure, it's the correct way if you are processing measured data, running simulations etc. But it's incorrect, for example, if you would like to calculate grades in school. In Hungary, we have a 5-grade grading system, and an average of 4.5 is rounded up to 5. I used this as an example when teaching my son Python, and I was stunned when round(4.5) gave 5. I had a hard time explaining to him why Python rounding works differently from what he learned in school about rounding...
  • Rocket Nikita
    Rocket Nikita almost 3 years
    Does not add any value to other answers.
  • TeaCoast
    TeaCoast almost 3 years
    @RocketNikita Epic.
  • drws
    drws over 2 years
    It does round 'up or down', but unfortunately doesn't work with negative numbers.
  • Uber Kluger
    Uber Kluger over 2 years
    Strictly speaking, both 0.49999999999999994 and 5000000000000001.0 are problematic due to precision. In both cases, adding 0.5 causes necessary precision bits to "fall off" the right hand side of the IEEE 754 double (64bit) mantissa (52 fraction bits + implicit 1.0). The first case basically doubles the value, pushing the (set) LSB out, while the second case is so large that the 0.5 is smaller than the existing LSB value. In fact, for 2^52 <= num < 2^53, it will round integers to even (prob. due to math chip rounding 80bit internal back to 64bit output). num >= 2^53 adding 0.5 does nothing.
  • Uber Kluger
    Uber Kluger over 2 years
  • akushyn
    akushyn over 2 years
    Great function! Never thought I'll face such an issue with rounding 133.125 with 2 digits to 133.13 nor to 133.12 . Thanks, Man!
  • Raihan Shafique
    Raihan Shafique almost 2 years
    thanks, super useful - directly used the 2nd code in the pandas df easily with apply df['round_halfup'] = df['original_column'].apply(lambda value: int(value + 0.5))