round() 似乎没有正确舍入

发布于 2024-07-04 16:56:38 字数 641 浏览 6 评论 0原文

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

n = 5.59
round(n, 1) # 5.6

但是,实际上,古老的浮点怪异现象悄然出现,你会得到:

5.5999999999999996

出于 UI 的目的,我需要显示 5.6。 我在互联网上浏览了一下,发现了一些 文档取决于我的Python实现。 不幸的是,这种情况发生在我的 Windows 开发机器和我尝试过的每台 Linux 服务器上。 另请参阅此处

除了创建自己的圆形库之外,还有什么办法可以解决这个问题吗?

The documentation for the round() function states that you pass it a number, and the positions past the decimal to round. Thus it should do this:

n = 5.59
round(n, 1) # 5.6

But, in actuality, good old floating point weirdness creeps in and you get:

5.5999999999999996

For the purposes of UI, I need to display 5.6. I poked around the Internet and found some documentation that this is dependent on my implementation of Python. Unfortunately, this occurs on both my Windows dev machine and each Linux server I've tried. See here also.

Short of creating my own round library, is there any way around this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(21

剩一世无双 2024-07-11 16:56:42

仅当最后一位数字为 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

The problem is only when last digit is 5. Eg. 0.045 is internally stored as 0.044999999999999... You could simply increment last digit to 6 and round off. This will give you the desired results.

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
何以心动 2024-07-11 16:56:42

作品完美

format(5.59, '.1f') # to display
float(format(5.59, '.1f')) #to round

Works Perfect

format(5.59, '.1f') # to display
float(format(5.59, '.1f')) #to round
只怪假的太真实 2024-07-11 16:56:42

代码:

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

Code:

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'

Output:

5.6
5.7
混吃等死 2024-07-11 16:56:42

关于什么:

round(n,1)+epsilon

What about:

round(n,1)+epsilon
如若梦似彩虹 2024-07-11 16:56:42

我在使用 round() 时遇到了同样的错误。 现在,我像这样使用 ceil() 或 Floor() :

import math
x = 76.5
x_str = str(x)
dot_index = x_str.index('.')
if(int(x_str[dot_index+1:]) >= 5):
  x = math.ceil(x)
else:
  x = math.floor(x)

I had the same error when using round(). Now, I use ceil() or floor() like so:

import math
x = 76.5
x_str = str(x)
dot_index = x_str.index('.')
if(int(x_str[dot_index+1:]) >= 5):
  x = math.ceil(x)
else:
  x = math.floor(x)
毁虫ゝ 2024-07-11 16:56:42

这是一种将浮点数四舍五入到任意小数位的简单方法,并且在 2021 年仍然有效!

float_number = 12.234325335563
rounded = round(float_number, 3) # 3 is the number of decimal places to be returned.You can pass any number in place of 3 depending on how many decimal places you want to return.
print(rounded)

这将打印出来;

12.234

Here is an easy way to round a float number to any number of decimal places, and it still works in 2021!

float_number = 12.234325335563
rounded = round(float_number, 3) # 3 is the number of decimal places to be returned.You can pass any number in place of 3 depending on how many decimal places you want to return.
print(rounded)

And this will print;

12.234
你没皮卡萌 2024-07-11 16:56:42

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

Here's where I see round failing. What if you wanted to round these 2 numbers to one decimal place?
23.45
23.55
My education was that from rounding these you should get:
23.4
23.6
the "rule" being that you should round up if the preceding number was odd, not round up if the preceding number were even.
The round function in python simply truncates the 5.

み零 2024-07-11 16:56:42

我正在做:

int(round( x , 0))

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

所以

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

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

I am doing:

int(round( x , 0))

In this case, we first round properly at the unit level, then we convert to integer to avoid printing a float.

so

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

I think this answer works better than formating the string, and it also makes more sens to me to use the round function.

绮筵 2024-07-11 16:56:42

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

mystring = "%.2f" % 5.5999

You can use the string format operator %, similar to sprintf.

mystring = "%.2f" % 5.5999
巷雨优美回忆 2024-07-11 16:56:42

另一个潜在的选择是:

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)

Another potential option is:

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)
浅唱々樱花落 2024-07-11 16:56:42

在这种情况下,我会完全避免依赖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

希望有所帮助。

I would avoid relying on round() at all in this case. Consider

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

will output

61.3
1.29

which is not a desired output if you need solid rounding to the nearest integer. To bypass this behavior go with math.ceil() (or math.floor() if you want to round down):

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)

outputs

61.3
1.3

Hope that helps.

平定天下 2024-07-11 16:56:41

printf 傻瓜。

print '%.1f' % 5.59  # returns 5.6

printf the sucker.

print '%.1f' % 5.59  # returns 5.6
一袭白衣梦中忆 2024-07-11 16:56:41

这确实是一个大问题。 试试这个代码:

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 的幂的分数的最接近的有限和。

It's a big problem indeed. Try out this code:

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

It displays 4.85. Then you do:

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

and it shows 4.8. Do you calculations by hand the exact answer is 4.85, but if you try:

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

you can see the truth: the float point is stored as the nearest finite sum of fractions whose denominators are powers of two.

岁月静好 2024-07-11 16:56:41

浮点数学很容易出现轻微但烦人的精度误差。 如果您可以使用整数或定点,则将保证精度。

Floating point math is vulnerable to slight, but annoying, precision inaccuracies. If you can work with integer or fixed point, you will be guaranteed precision.

紫竹語嫣☆ 2024-07-11 16:56:41

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

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

然后通过插入区域设置的小数分隔符来显示数字。

然而,吉米的答案更好。

You can switch the data type to an integer:

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

And then display the number by inserting the locale's decimal separator.

However, Jimmy's answer is better.

不爱素颜 2024-07-11 16:56:41

看一下 Decimal 模块

十进制“基于浮点
与人一起设计的模型
记在心上,并且必然有一个
最重要的指导原则——
计算机必须提供算术运算
其工作方式与
人们学习的算术
学校。” – 摘自小数
算术规范。

可以表示十进制数
确切地。 相比之下,像 1.1 这样的数字
2.2 没有确切的
二进制浮点表示
观点。 最终用户通常不会
期望 1.1 + 2.2 显示为
3.3000000000000003,与二进制浮点数相同。

Decimal 提供了一种运算,可以轻松编写需要浮点运算的应用程序,并且还需要以人类可读的格式(例如会计)呈现这些结果。

Take a look at the Decimal module

Decimal “is based on a floating-point
model which was designed with people
in mind, and necessarily has a
paramount guiding principle –
computers must provide an arithmetic
that works in the same way as the
arithmetic that people learn at
school.” – excerpt from the decimal
arithmetic specification.

and

Decimal numbers can be represented
exactly. In contrast, numbers like 1.1
and 2.2 do not have an exact
representations in binary floating
point. End users typically would not
expect 1.1 + 2.2 to display as
3.3000000000000003 as it does with binary floating point.

Decimal provides the kind of operations that make it easy to write apps that require floating point operations and also need to present those results in a human readable format, e.g., accounting.

浅笑轻吟梦一曲 2024-07-11 16:56:41

如果执行 str(round(n, 1)) 而不仅仅是 round(n, 1),则会得到“5.6”。

You get '5.6' if you do str(round(n, 1)) instead of just round(n, 1).

你的往事 2024-07-11 16:56:41

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

>>> 5.6
5.5999999999999996
>>> 

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

如果您需要的话,Python 有一个十进制算术模块

round(5.59, 1) is working fine. The problem is that 5.6 cannot be represented exactly in binary floating point.

>>> 5.6
5.5999999999999996
>>> 

As Vinko says, you can use string formatting to do rounding for display.

Python has a module for decimal arithmetic if you need that.

山有枢 2024-07-11 16:56:40

如果您使用 Decimal 模块,您可以在不使用“round”函数的情况下进行近似。 这是我一直使用的舍入方法,尤其是在编写货币应用程序时:

from decimal import Decimal, ROUND_UP

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

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

If you use the Decimal module you can approximate without the use of the 'round' function. Here is what I've been using for rounding especially when writing monetary applications:

from decimal import Decimal, ROUND_UP

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

This will return a Decimal Number which is 16.20.

梦过后 2024-07-11 16:56:39

即使无需舍入,格式也能正常工作:

"%.1f" % n

Formatting works correctly even without having to round:

"%.1f" % n
故事与诗 2024-07-11 16:56:38

我无法改变它的存储方式,但至少格式可以正常工作:

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

I can't help the way it's stored, but at least formatting works correctly:

'%.1f' % round(n, 1) # Gives you '5.6'
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文