如何在Python中正确地舍入一半浮点数?

时间:2015-10-08 15:11:43

标签: python python-3.x floating-point rounding precision

我正面临round()功能的奇怪行为:

for i in range(1, 15, 2):
    n = i / 2
    print(n, "=>", round(n))

此代码打印:

0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6

我预计浮动值总是向上舍入,而是四舍五入到最接近的偶数。

为什么会出现这种行为,以及获得正确结果的最佳方法是什么?

我尝试使用fractions,但结果是一样的。

18 个答案:

答案 0 :(得分:33)

Numeric Types section明确记录了此行为:

  

round(x[, n])
   x 舍入为n位数,舍入为偶数的一半。如果省略n,则默认为0.

注意舍入一半甚至。这也被称为银行家四舍五入;而不是总是向上或向下舍入(复合舍入误差),通过舍入到最接近的偶数数字,您可以平均舍入误差。

如果您需要更好地控制舍入行为,请使用decimal module,这样您就可以准确指定rounding strategy should be used

例如,从一半向上舍入:

>>> 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

答案 1 :(得分:23)

例如:

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)

答案 2 :(得分:12)

round()将向上或向下舍入,具体取决于数字是偶数还是奇数。只围绕一个简单的方法是:

int(num + 0.5)

如果您希望此功能正确用于负数,请使用:

((num > 0) - (num < 0)) * int(abs(num) + 0.5)

请注意,这可能会导致大数字或真正精确的数字,例如5000000000000001.00.49999999999999994

答案 3 :(得分:10)

您可以使用:

import math
def normal_round(n):
    if n - math.floor(n) < 0.5:
        return math.floor(n)
    return math.ceil(n)

它会正确地向上或向下舍入数字。

答案 4 :(得分:5)

您看到的行为是典型的IEEE 754舍入行为。如果它必须在两个与输入不同的数字之间进行选择,它总是选择偶数。这种行为的优点是平均舍入效应为零 - 同样多个数字向上和向下舍入。如果您以一致的方向对中间数字进行舍入,则舍入将影响预期值。

如果目标是公平的舍入,那么您所看到的行为是正确的,但这并不总是需要的。

获得所需舍入类型的一个技巧是添加0.5然后发言。例如,添加0.5到2.5会产生3,其中3楼。

答案 5 :(得分:3)

简短版本:使用decimal module。它可以精确地代表2.675这样的数字,不像Python浮点数,其中2.675 真的 2.67499999999999982236431605997495353221893310546875(确切地说)。并且您可以指定所需的舍入:ROUND_CEILING,ROUND_DOWN,ROUND_FLOOR,ROUND_HALF_DOWN,ROUND_HALF_EVEN,ROUND_HALF_UP,ROUND_UP和ROUND_05UP都是选项。

答案 6 :(得分:1)

所以为了确保这里有一个清晰的工作示例,我写了一个方便的小函数

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

以及一组合适的测试用例

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

答案 7 :(得分:0)

没有任何库的经典数学舍入

array = Array.from(array);

答案 8 :(得分:0)

在某些情况下,一些解决方案的四舍五入可能无法按预期工作。

例如使用上面的函数:

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

我期待 4.4 的地方。首先将 x 转换为字符串对我来说有什么技巧。

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

知道round(9.99,0)舍入到int=10并且int(9.99)舍入到int=9会带来成功:

目标:根据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

一些测试:

enter image description here

yw

答案 11 :(得分:0)

在这个问题上,当将正整数除以2时,这基本上是一个问题。最简便的方法是对单个数字使用int(n + 0.5)

但是我们不能将其应用到系列中,因此我们可以在不进入循环的情况下例如对熊猫数据框进行以下操作:

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))

答案 12 :(得分:0)

您可以尝试

def round(num):
    return round(num + 10**(-9))

它将起作用,因为在此过程中,num = x.5将始终为x.5 + 0.00...01,因此它会更接近x+1,因此,舍入功能将正常工作,并且会将x.5舍入为x+1

答案 13 :(得分:0)

以下解决方案无需使用Dim str = "Car damage $10,000 for third party" 'Create regex which matches three groups: 'Group 1: (.*) ==> matches any characters 'Group 2: (\$[\d,]+) ==> matches $ followed by one or more digits and/or comma (,) 'Group 3: (.*) ==> matches any characters Dim regex = New Regex("(.*)(\$[\d,]+)(.*)") Dim match = regex.Match(str) 'Get values for all groups, skipping the first one which matches the whole string. Dim parts = match.Groups.Cast(Of Group).Skip(1).Select(Function(g) g.Value).ToArray() 模块(事实证明很慢)即可实现“学校时尚四舍五入”。

decimal

例如

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)

答案 14 :(得分:0)

这是另一种解决方案。 它将像在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

答案 15 :(得分:0)

喜欢fedor2612的答案。对于那些想使用此函数舍入任意数量的小数的人,我使用了一个可选的“ decimals”参数对其进行了扩展(例如,如果您想将26.455美元的货币舍入为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)

输出:

  

26.45

     

26.46

答案 16 :(得分:0)

您可以使用:

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))

答案 17 :(得分:-1)

为什么要搞得这么复杂?

def HalfRoundUp(value):
    return int(value + 0.5)

你当然可以把它变成一个 lambda 表达式:

HalfRoundUp = lambda value: int(value + 0.5)