round()似乎无法正确舍入


123

round()函数的文档指出,您向其传递了一个数字,并将小数点后的位置传递给四舍五入。因此,它应该这样做:

n = 5.59
round(n, 1) # 5.6

但是,实际上,老的浮点怪异现象不断蔓延,您会得到:

5.5999999999999996

出于UI的目的,我需要显示5.6。我在Internet上闲逛,发现一些文档取决于我对Python的实现。不幸的是,这在我的Windows开发机和我尝试过的每台Linux服务器上都会发生。另请参阅此处

除了创建自己的回合库之外,还有什么办法可以解决?


4
我用python 2.7.11 round(5.59)进行了尝试,在Windows和linux x86 64位计算机Cython上都给出了5.6的结果?(我想现在提到的文档链接已更改)
Alex Punnen

2
实际上不能正常工作的地方是round(5.55, 1) = 5.5
德米特里

Answers:


101

我不知道它的存储方式,但至少格式化正确:

'%.1f' % round(n, 1) # Gives you '5.6'

11
我尝试过print '%.2f' % 655.665但它返回了655.66,应该是655.67
Liza

1
@Kyrie参见stackoverflow.com/questions/9301690/…。归咎于浮点数错误-“ 5.665-> 5.67”,但“ 15.665-> 15.66”。如果需要精确的精度,请使用小数。
吉米

7
这在搜索后有效:) from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN#用于舍入浮点数Decimal(str(655.665)).quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)#浮点数的问题和局限性
Liza 2015年

102

格式化无需四舍五入即可正确进行:

"%.1f" % n

18
根据文档,这种样式的字符串格式最终将消失。新样式的格式为"{:.1f}".format(n)
whereswalden 2014年

2
不能正确舍入:'%.5f' % 0.988625给出0.98862
schlamar

@schlamar:这也是round()的行为:round(0.988625,5)也给出0.98862。回合(0.988626,5)以及“%.5f”%0.988626得到0.98863
Vinko Vrsalovic

不幸的是,“%。2f”%2.675将返回2.67-对于使用此方法并期望2.68的用户,这可能是一个意外的答案
Dion

30

如果使用小数模块,则无需使用“舍入”功能就可以近似。这是我用于舍入的内容,尤其是在编写货币应用程序时:

Decimal(str(16.2)).quantize(Decimal('.01'), rounding=ROUND_UP)

这将返回一个十进制数为16.20。


4
这是规范的答案 –无论如何,准确性在任何地方都很重要。当然:这有点冗长。但是把那个傻瓜扔进一个辅助函数中,你就很好格式化了。
塞西尔·库里

2
rounding='ROUND_UP'
LMc '18年

如果出现此错误NameError: global name 'ROUND_UP' is not defined,则需要导入舍入函数:from decimal import Decimal, ROUND_UP其他取整功能
Stephen Blair,

您的示例似乎仍然很危险:您依靠str()提供的舍入。
YvesgereY

21

round(5.59, 1)工作正常。问题在于5.6无法精确地用二进制浮点表示。

>>> 5.6
5.5999999999999996
>>> 

正如Vinko所说,您可以使用字符串格式对显示进行四舍五入。

如果需要,Python有一个用于十进制算术模块


1
Python 2.7或Python 3.5不再是问题
vy32


10

您可以将数据类型切换为整数:

>>> n = 5.59
>>> int(n * 10) / 10.0
5.5
>>> int(n * 10 + 0.5)
56

然后通过插入语言环境的小数点分隔符来显示数字。

但是,吉米的答案更好。


5

浮点数学容易受到轻微但令人讨厌的精度误差的影响。如果可以使用整数或定点,则可以保证精度。


5

看一下Decimal模块

十进制“基于浮点模型,该浮点模型是为人而设计的,并且必然具有最重要的指导原则–计算机必须提供一种与人们在学校学习的算法相同的算法。” –摘自十进制算术规范。

小数可以精确表示。相反,像1.1和2.2这样的数字在二进制浮点数中没有确切的表示形式。最终用户通常不会期望1.1 + 2.2像二进制浮点那样显示为3.3000000000000003。

Decimal提供了一种操作,使编写需要浮点运算的应用程序变得容易,并且需要以人类可读的格式(例如记帐)显示这些结果。



4

确实是个大问题。试用以下代码:

print "%.2f" % (round((2*4.4+3*5.6+3*4.4)/8,2),)

显示4.85。然后,您执行以下操作:

print "Media = %.1f" % (round((2*4.4+3*5.6+3*4.4)/8,1),)

它显示4.8。您手动计算的确切答案是4.85,但是如果尝试:

print "Media = %.20f" % (round((2*4.4+3*5.6+3*4.4)/8,20),)

您会看到事实:浮点存储为分母为2的幂的分数的最接近有限和。


3

您可以使用%类似于sprintf 的字符串格式运算符。

mystring = "%.2f" % 5.5999


2

我在做:

int(round( x , 0))

在这种情况下,我们首先在单位级别正确舍入,然后转换为整数以避免打印浮点数。

所以

>>> int(round(5.59,0))
6

我认为这个答案比格式化字符串更好,并且使用round函数对我也更有意义。


2

round()在这种情况下,我将完全避免依赖。考虑

print(round(61.295, 2))
print(round(1.295, 2))

将输出

61.3
1.29

如果您需要四舍五入到最接近的整数,则这不是理想的输出。要绕过此行为,请使用math.ceil()(或math.floor()如果要舍入):

from math import ceil
decimal_count = 2
print(ceil(61.295 * 10 ** decimal_count) / 10 ** decimal_count)
print(ceil(1.295 * 10 ** decimal_count) / 10 ** decimal_count)

输出

61.3
1.3

希望有帮助。


1

码:

x1 = 5.63
x2 = 5.65
print(float('%.2f' % round(x1,1)))  # gives you '5.6'
print(float('%.2f' % round(x2,1)))  # gives you '5.7'

输出:

5.6
5.7

0

这是我看到回合失败的地方。如果您想将这两个数字四舍五入到小数点后该怎么办?23.45 23.55我的教育是,通过对这些数字进行四舍五入,您将获得:23.4 23.6“规则”是,如果前一个数字为奇数,则应四舍五入,如果前一个数字为偶数,则不四舍五入。python中的round函数将截断5。


1
您所说的是“银行家四舍五入”,这是执行四舍五入的许多不同方式之一。
西蒙MᶜKenzie18年

0

问题仅在最后一位数字为5时出现。0.045在内部存储为0.044999999999999 ...您可以将最后一位数字简单地增加到6并四舍五入。这将为您提供所需的结果。

import re


def custom_round(num, precision=0):
    # Get the type of given number
    type_num = type(num)
    # If the given type is not a valid number type, raise TypeError
    if type_num not in [int, float, Decimal]:
        raise TypeError("type {} doesn't define __round__ method".format(type_num.__name__))
    # If passed number is int, there is no rounding off.
    if type_num == int:
        return num
    # Convert number to string.
    str_num = str(num).lower()
    # We will remove negative context from the number and add it back in the end
    negative_number = False
    if num < 0:
        negative_number = True
        str_num = str_num[1:]
    # If number is in format 1e-12 or 2e+13, we have to convert it to
    # to a string in standard decimal notation.
    if 'e-' in str_num:
        # For 1.23e-7, e_power = 7
        e_power = int(re.findall('e-[0-9]+', str_num)[0][2:])
        # For 1.23e-7, number = 123
        number = ''.join(str_num.split('e-')[0].split('.'))
        zeros = ''
        # Number of zeros = e_power - 1 = 6
        for i in range(e_power - 1):
            zeros = zeros + '0'
        # Scientific notation 1.23e-7 in regular decimal = 0.000000123
        str_num = '0.' + zeros + number
    if 'e+' in str_num:
        # For 1.23e+7, e_power = 7
        e_power = int(re.findall('e\+[0-9]+', str_num)[0][2:])
        # For 1.23e+7, number_characteristic = 1
        # characteristic is number left of decimal point.
        number_characteristic = str_num.split('e+')[0].split('.')[0]
        # For 1.23e+7, number_mantissa = 23
        # mantissa is number right of decimal point.
        number_mantissa = str_num.split('e+')[0].split('.')[1]
        # For 1.23e+7, number = 123
        number = number_characteristic + number_mantissa
        zeros = ''
        # Eg: for this condition = 1.23e+7
        if e_power >= len(number_mantissa):
            # Number of zeros = e_power - mantissa length = 5
            for i in range(e_power - len(number_mantissa)):
                zeros = zeros + '0'
            # Scientific notation 1.23e+7 in regular decimal = 12300000.0
            str_num = number + zeros + '.0'
        # Eg: for this condition = 1.23e+1
        if e_power < len(number_mantissa):
            # In this case, we only need to shift the decimal e_power digits to the right
            # So we just copy the digits from mantissa to characteristic and then remove
            # them from mantissa.
            for i in range(e_power):
                number_characteristic = number_characteristic + number_mantissa[i]
            number_mantissa = number_mantissa[i:]
            # Scientific notation 1.23e+1 in regular decimal = 12.3
            str_num = number_characteristic + '.' + number_mantissa
    # characteristic is number left of decimal point.
    characteristic_part = str_num.split('.')[0]
    # mantissa is number right of decimal point.
    mantissa_part = str_num.split('.')[1]
    # If number is supposed to be rounded to whole number,
    # check first decimal digit. If more than 5, return
    # characteristic + 1 else return characteristic
    if precision == 0:
        if mantissa_part and int(mantissa_part[0]) >= 5:
            return type_num(int(characteristic_part) + 1)
        return type_num(characteristic_part)
    # Get the precision of the given number.
    num_precision = len(mantissa_part)
    # Rounding off is done only if number precision is
    # greater than requested precision
    if num_precision <= precision:
        return num
    # Replace the last '5' with 6 so that rounding off returns desired results
    if str_num[-1] == '5':
        str_num = re.sub('5$', '6', str_num)
    result = round(type_num(str_num), precision)
    # If the number was negative, add negative context back
    if negative_number:
        result = result * -1
    return result

0

另一个可能的选择是:

def hard_round(number, decimal_places=0):
    """
    Function:
    - Rounds a float value to a specified number of decimal places
    - Fixes issues with floating point binary approximation rounding in python
    Requires:
    - `number`:
        - Type: int|float
        - What: The number to round
    Optional:
    - `decimal_places`:
        - Type: int 
        - What: The number of decimal places to round to
        - Default: 0
    Example:
    ```
    hard_round(5.6,1)
    ```
    """
    return int(number*(10**decimal_places)+0.5)/(10**decimal_places)

-4

关于什么:

round(n,1)+epsilon

仅当四舍五入始终与epsilon的四舍五入相抵时才有效。如果epsilon = .000001这样,round(1.0/5.0, 1) + epsilon则将精确表示形式设为0.2,并将其设为0.00001。如果epsilon在round函数内部,同样会出现严重问题。
迈克尔·斯科特·库斯伯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.