Python中保留小数点后两位的四舍五入

67

我该如何在Python中将一个数字四舍五入到小数点后第二位?例如:

0.022499999999999999

应该四舍五入到0.03

0.1111111111111000

应该向上舍入为0.12

如果第三位小数有任何值,我希望它总是向上舍入,使我保留小数点后两位。


5
在继续之前,我建议您阅读Python教程中的《浮点数算术:问题与限制》一文。请注意,翻译时不会附带解释或其他内容。 - Sven Marnach
此外,考虑一下是否真的想要舍入值,还是只想用三位小数 显示 它们... - Wooble
https://dev59.com/1nRB5IYBdhLWcg3w9Lzn#63035788 - NMC
13个回答

55

Python 包括 round() 函数,允许你指定 你想要的小数位数。来自文档:

round(x[, n])

返回四舍五入到小数点后 n 位的浮点数 x。如果省略了 n,则默认为零。结果是一个浮点数。值被四舍五入到最接近的10的负n次幂的倍数;如果两个倍数距离一样,舍入会远离0(例如,round(0.5) 是 1.0,round(-0.5) 是 -1.0)。

因此,您需要使用 round(x, 2) 进行普通舍入。要确保始终向上舍入数字,您需要使用 ceil(x) 函数。同样,要向下舍入,请使用 floor(x)


23
好的建议,但它不能像原帖作者所需的那样向上取整。 - NPE
1
“Round up”不同于普通的四舍五入。请看问题中的例子。 - Mark Ransom
@Mark 因为加入实现 ceil 的代码会意味着抄袭你的答案,所以我将我的留在原样并点赞你的回答。 - simchona

38
from math import ceil

num = 0.1111111111000
num = ceil(num * 100) / 100.0

参见:
math.ceil文档
round文档 - 无论如何,您将来可能也需要查看此文档。


你这里不需要使用 round() -- 它不会以任何方式改变结果。 - Sven Marnach
11
以下情况会得出错误结果:math.ceil((1.11 * 100.0)) / 100.0结果为 1.12原因是: 1.11 * 100.0 的值为 111.00000000000001 - nimeshkiranverma
2
@nimeshkiranverma 请查看浮点数运算是否存在问题? 如果您需要精确的十进制结果,请使用decimal模块 - Mark Ransom
1
根据@nimeshkiranverma的观察,您确实需要使用round()才能获得所需的结果:math.ceil(round(1.11 * 100.0)) / 100.0 - Sean Bearden

27
x = math.ceil(x * 100.0) / 100.0

3
我必须盯着这个东西看了一会儿,才意识到这比我的解决方案更具Python风格。 - Edwin

22

更新的答案:
正如@jpm在评论中指出的那样,我原来的答案存在边界问题。 Python 3 更加困难,因为它使用“银��家”舍入而不是“老派”的四舍五入。然而,在研究这个问题时,我发现了一个更好的解决方案,使用 decimal 库。

import decimal

def round_up(x, place=0):
    context = decimal.getcontext()
    # get the original setting so we can put it back when we're done
    original_rounding = context.rounding
    # change context to act like ceil()
    context.rounding = decimal.ROUND_CEILING

    rounded = round(decimal.Decimal(str(x)), place)
    context.rounding = original_rounding
    return float(rounded)

或者如果你真的只想要一行代码:

import decimal
decimal.getcontext().rounding = decimal.ROUND_CEILING

# here's the one-liner
float(round(decimal.Decimal(str(0.1111)), ndigits=2))
>> 0.12

# Note: this only affects the rounding of `Decimal`
round(0.1111, ndigits=2)
>> 0.11

以下是一些示例:

round_up(0.022499999999999999, 2)
>> 0.03
round_up(0.1111111111111000, 2)
>> 0.12
round_up(0.1111111111111000, 3)
>> 0.112

round_up(3.4)
>> 4.0

# @jpm - boundaries do what we want
round_up(0.1, 2)
>> 0.1
round_up(1.1, 2)
>> 1.1

# Note: this still rounds toward `inf`, not "away from zero"
round_up(2.049, 2)
>> 2.05
round_up(-2.0449, 2)
>> -2.04

我们还可以使用它来对小数点左侧进行四舍五入:

round_up(11, -1)
>> 20

我们不乘以10,从而避免了这个答案中提到的溢出问题。

round_up(1.01e308, -307)
>> 1.1e+308
原始回答(不建议使用)
这取决于您在考虑正数和负数时想要的行为,但是如果您希望始终将其四舍五入为较大的值(例如2.0449-> 2.05,-2.0449-> -2.04),则可以执行以下操作:
round(x + 0.005, 2)

或者更加花哨一点:

def round_up(x, place):
    return round(x + 5 * 10**(-1 * (place + 1)), place)

这似乎也可以如下运作:

round(144, -1)
# 140
round_up(144, -1)
# 150
round_up(1e308, -307)
# 1.1e308

1
这并不保证可行... 例如:round(0.1 + 0.005, 2) == 0.11,而 round(1.1 + 0.005, 2) == 1.10 - jpm
@jpm:好观点。我研究了一下,发现使用round函数处理边界问题特别困难,尤其是现在round函数使用“银行家舍入”而非“传统舍入”。我已经更新了我的答案,提供了更好的解决方案。 - ajp619
这个答案可以通过指定“oldschool”所指的舍入方式并去掉银行家舍入周围的引号来进行改进:https://en.wikipedia.org/wiki/Rounding。 - Hakaishin

15

参考 Edwin 的回答来推断:

from math import ceil, floor
def float_round(num, places = 0, direction = floor):
    return direction(num * (10**places)) / float(10**places)

使用方法:

>>> float_round(0.21111, 3, ceil)  #round up
>>> 0.212
>>> float_round(0.21111, 3)        #round down
>>> 0.211
>>> float_round(0.21111, 3, round) #round naturally
>>> 0.211

8
以下情况可能导致计算结果错误:math.ceil((1.11 * 100.0)) / 100.0 的结果为 1.12原因是: 1.11 * 100.0 的值为 111.00000000000001 - nimeshkiranverma

5
请注意,ceil(num * 100) / 100 的技巧在某些特殊输入下会崩溃,例如1e308。虽然这种情况可能不经常出现,但我可以告诉你,它让我浪费了几天时间。为了避免这种情况,“如果” ceil()floor() 带有类似于 round() 的小数位参数会很好...同时,有人知道一个干净的替代方法,它不会在这样的输入上崩溃吗?我对 decimal 包也有一些希望,但它似乎也无法正常工作:
>>> from math import ceil
>>> from decimal import Decimal, ROUND_DOWN, ROUND_UP
>>> num = 0.1111111111000
>>> ceil(num * 100) / 100
0.12
>>> float(Decimal(num).quantize(Decimal('.01'), rounding=ROUND_UP))
0.12
>>> num = 1e308
>>> ceil(num * 100) / 100
Traceback (most recent call last):
  File "<string>", line 301, in runcode
  File "<interactive input>", line 1, in <module>
OverflowError: cannot convert float infinity to integer
>>> float(Decimal(num).quantize(Decimal('.01'), rounding=ROUND_UP))
Traceback (most recent call last):
  File "<string>", line 301, in runcode
  File "<interactive input>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]

当然,有人可能会说,在这种输入情况下崩溃是唯一明智的行为,但我认为造成问题的不是四舍五入,而是乘法(这就是为什么1e306不会崩溃),因此更干净的实现四舍五入到第N位的函数可以避免乘法的hack。


你在使用 Decimal 的过程中遇到的问题是量化表达式的结果需要311位数字来表达,而当前精度太小。如果您事先执行from decimal import getcontext; getcontext().prec = 400,这将“起作用”,对于某些值有效。或者您可以注意到,绝对值大于$2^{52}$的任何浮点数必须已经是整数,因此四舍五入不会产生任何影响。是的,我同意如果“ceil”和“floor”带有小数位参数会更好。 - Mark Dickinson

5

Python中的round函数可能会对你期望的进行不同的四舍五入。

你可以通过使用Decimal.quantize来更具体地指定四舍五入方法。

例如:

Original Answer翻译成“最初的回答”。

from decimal import Decimal, ROUND_HALF_UP
res = Decimal('0.25').quantize(Decimal('0.0'), rounding=ROUND_HALF_UP)
print(res) 
# prints 0.3

更多参考资料:

https://gist.github.com/jackiekazil/6201722

最初的回答:


3

这里有一个更通用的一行代码,适用于任何数字:

import math
def ceil(number, digits) -> float: return math.ceil((10.0 ** digits) * number) / (10.0 ** digits)

示例用法:

>>> ceil(1.111111, 2)
1.12

注意:正如nimeshkiranverma所述:

>>> ceil(1.11, 2) 
1.12  #Because: 1.11 * 100.0 has value 111.00000000000001

2
def round_up(number, ndigits=None):
    # start by just rounding the number, as sometimes this rounds it up
    result = round(number, ndigits if ndigits else 0)
    if result < number:
        # whoops, the number was rounded down instead, so correct for that
        if ndigits:
            # use the type of number provided, e.g. float, decimal, fraction
            Numerical = type(number)
            # add the digit 1 in the correct decimal place
            result += Numerical(10) ** -ndigits
            # may need to be tweaked slightly if the addition was inexact
            result = round(result, ndigits)
        else:
            result += 1 # same as 10 ** -0 for precision of zero digits
    return result

assert round_up(0.022499999999999999, 2) == 0.03
assert round_up(0.1111111111111000, 2) == 0.12

assert round_up(1.11, 2) == 1.11
assert round_up(1e308, 2) == 1e308

1
你应该在代码中添加注释来描述你所做的事情。 - Michael
这样好一些吗? - David Augusto Villa

0

我写了一个简单的函数来进行向上取整:

def round_up(number: float, ndigits: int):
    offset = 0.5
    if ndigits and ndigits > 0:
        offset = offset / (10 ** ndigits)
        return round(number + offset, ndigits)
   else:
       return round(number+offset)

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