Python不一致地进行四舍五入

12
如果我告诉 Python v. 3.4.3,round(2.5),那么它会输出 2。如果我告诉它 round(1.5),那么它也会输出 2。同样地,round(3.5)给出的是4,而round(4.5)也给出4。但是,我需要 Python 能够进行一致的四舍五入。具体而言,每当我输入一个介于两个整数之间的数字,它都要进行四舍五入。因此,round(1.5) = 1round(2.5) = 2,而round(1.6) = 2 等等。
如何解决这个问题?
编辑:我已经阅读了 round 函数的文档,并理解了这是其预期行为。我的问题是,我如何改变这种行为,因为我需要将 1.5 向下舍入以用于我的目的。

4
可能是 Python 3.x 四舍五入行为 的重复问题。 - tzaman
请注意,这并不完全是重复的内容:OP主要是被Python 3的四舍五入方式所困惑,而不是它与Python 2的差异。 - user707650
这是对大多数Python新手的重要观察。虽然一开始会感到惊讶,但Python 3的舍入比你想象的要更加一致 - Wolf
7个回答

6
Python 3在四舍五入方面与Python 2有所不同:它现在使用所谓的“银行家舍入”(维基百科):当整数部分为奇数时,数字被向远离零的方向舍入;当整数部分为偶数时,数字被向靠近零的方向舍入。
其原因是避免偏差,当所有值为.5时,它们都会从零开始向外舍入(然后进行求和等操作)。
这就是您所看到的行为,事实上是一致的。它只是与您习惯的方式有所不同。

这并不是一个习惯的问题。对于我的应用程序来说,我实际上需要它始终将数字像1.5、2.5、3.5等四舍五入到最低整数。有没有办法修改“round”函数来实现这个需求? - James Pirlman
@JamesPirlman 如果你需要在处理非整数数字时具有特定的行为,你真的需要使用Decimal模块,就像我和其他人提到的那样。这不仅仅是关于round函数,而是关于计算机如何处理浮点数的一般性问题。请参阅我的答案 - justinpawela
所谓的“银行家舍入法”在这种情况下最好被替换为更精确的方法,它在IEEE-754中定义,维基百科称:“这是IEEE 754计算函数和运算符中使用的默认舍入模式(另请参见最近整数函数)。” - Wolf

4
round文档中有关于浮点数四舍五入的细节说明。
您可以使用decimal库来实现您想要的功能。
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN

round(2.675, 2)
# output: 2.67

Decimal('2.675').quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)
# output: 2.68

Decimal('2.5').quantize(Decimal('1.'), rounding=ROUND_HALF_DOWN)
# output: 2

3
您想要“向下取整”,但实际得到的是“四舍五入”。 可以通过手动执行以下操作来实现:
您想要“向下取整”,但实际得到的是“四舍五入”。 可以通过手动执行以下操作来实现:
ceil(x - 0.5)

很好的回答更新问题,我想点赞它,但我发现缺少一个关于原始主题的词。 - Wolf

1
Python 3提供了在IEEE浮点算术标准(IEEE 754)中定义的舍入方法,缺省的舍入[1]是指向最近的数字并最小化累积误差。
在IEEE 754中,定义了5种方法,其中两种是四舍五入到最近的数(Python提供第一种方法通过round),另外三种方法是明确指定方向(Python在其Math模块中有truncceilfloor)。
显然你需要一个明确的舍入方式,而Python有一种方法可以告诉它,你只需选择即可。

[1]由于计算机中浮点数的表示是有限的,所以舍入并不像您想象的那样简单,您会感到惊讶!我建议仔细阅读Python 3文档中的15.浮点算术:问题和限制


1

这个文档写得非常好。根据Python文档中的round:

注意 对于浮点数,round()的行为可能会让人感到惊讶:例如,round(2.675, 2)返回的是2.67而不是预期的2.68。这不是一个错误:这是由于大多数十进制小数不能完全表示为浮点数的结果。参见浮点运算:问题和限制以获取更多信息

具体而言,这是计算机通常处理浮点数的副作用。

如果您需要更高的精度,包括不同的舍入方式,建议您查看Python Decimal模块。特别有趣的是,它们可以控制舍入模式。看起来你可能想要 decimal.ROUND_HALF_DOWN


0

我相信我已经找到了所有人遇到的舍入误差的答案。我编写了自己的方法,它与“round”函数相同,但实际上会查看最后一位数字,并逐个情况进行四舍五入。不需要将十进制转换为二进制。它可以处理小数点后任意数量的数字,并且还可以接受科学计数法(由浮点数输出)。它也不需要任何导入!如果您发现任何不起作用的情况,请告诉我!

def Round(Number,digits = 0):
Number_Str = str(Number)
if "e" in Number_Str:
    Number_Str = "%.10f" % float(Number_Str)
if "." in Number_Str:   #If not given an integer
    try:
        Number_List = list(Number_Str)  #All the characters in Number in a list
        Number_To_Check = Number_List[Number_List.index(".") + 1 + digits]  #Gets value to be looked at for rounding.      
        if int(Number_To_Check) >= 5:
            Index_Number_To_Round = Number_List.index(".") + digits
            if Number_List[Index_Number_To_Round] == ".":
                Index_Number_To_Round -= 1
            if int(Number_List[Index_Number_To_Round]) == 9:
                Number_List_Spliced = Number_List[:Number_List.index(".")+digits]
                for index in range(-1,-len(Number_List_Spliced) - 1,-1):
                    if Number_List_Spliced[index] == ".":
                        continue
                    elif int(Number_List_Spliced[index]) == 9:
                        Number_List_Spliced[index] = "0"
                        try:
                            Number_List_Spliced[index-1]
                            continue
                        except IndexError:
                            Number_List_Spliced.insert(0,"1")
                    else:
                        Number_List_Spliced[index] = str(int(Number_List_Spliced[index])+1)
                        break
                FinalNumber = "".join(Number_List_Spliced)
            else:
                Number_List[Index_Number_To_Round] = str(int(Number_List[Index_Number_To_Round])+1)
                FinalNumber = "".join(Number_List[:Index_Number_To_Round + 1])
            return float(FinalNumber)
        else:
            FinalNumber = "".join(Number_List[:Number_List.index(".") + 1 + digits])
            return float(FinalNumber)
    except IndexError:
        return float(Number)
else:   #If given an integer
    return float(Number)

1
我看到这个混乱的情况时感到震惊。 - Ekrem Dinçel

0
这是一个函数,可以让你始终进行四舍五入, 如果你想向下取整,请将 round_down 设置为 True。
def common_round(float_number, digit_to_round_to=0, round_down=False):

float_splits = str(float_number).split(".")

# Checks if number not really a float, returns the int:
if len(float_splits) == 1:
    y = float_splits
    return y

else:
    # Looks for the rounding digit:
    float_part = float_splits[-1]
    float_part_sliced = float_part[:(digit_to_round_to + 1)]
    rounding_digit = int(float_part_sliced[-1])

    if rounding_digit != 5:
        # If rounding_digit is not 5, just do round():
        y = round(float_number, digit_to_round_to)
        return y

    else:
        # If rounding_digit is 5, add or substract 1 to rounding digit:
        if round_down == False:
            rounding_digit += 1
        else:
            rounding_digit -= 1

        # Paste number back together and do round():
        reconstructed_str = float_splits[0] + '.' + float_part_sliced[:-1] + str(rounding_digit)
        reconstructed_float = float(reconstructed_str)
        y = round(reconstructed_float, digit_to_round_to)
        return y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接