Python 3.x 四舍五入

Python 3.x rounding half up

我知道关于 python 中舍入的问题已经被问过多次,但答案对我没有帮助。我正在寻找一种将浮点数向上舍入一半并且 returns 是一个浮点数的方法。该方法还应该接受一个参数,该参数定义要四舍五入的小数位。我写了一个实现这种舍入的方法。但是,我认为它看起来一点也不优雅。

def round_half_up(number, dec_places):
    s = str(number)

    d = decimal.Decimal(s).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

我不喜欢它,我必须将 float 转换为字符串(以避免浮点不准确),然后使用 decimal 模块。 你有更好的解决方案吗?

编辑:正如下面的答案所指出的,我的问题的解决方案并不那么明显,因为正确的舍入首先需要正确表示数字,而浮点数不是这种情况。所以我希望下面的代码

def round_half_up(number, dec_places):

    d = decimal.Decimal(number).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

(与上面代码的不同之处在于浮点数直接转换为十进制数而不是先转换为字符串)到 return 2.18 像这样使用时: round_half_up(2.175, 2) 但是不会,因为Decimal(2.175)会return Decimal('2.17499999999999982236431605997495353221893310546875'),计算机表示浮点数的方式。 令人惊讶的是,第一个代码 returns 2.18 因为浮点数首先转换为字符串。似乎 str() 函数对最初打算舍入的数字进行了隐式舍入。所以会发生两次舍入。尽管这是我所期望的结果,但它在技术上是错误的。

在尝试了很长时间来生成一个优雅的单行函数之后,我最终得到了一个大小与字典相当的东西。

我想说最简单的方法就是

def round_half_up(inp,dec_places):
    return round(inp+0.0000001,dec_places)

我承认这在所有情况下都不准确,但如果您只是想要一个简单的快速解决方法,应该可行。

四舍五入出奇地难,因为您必须非常小心地处理浮点计算。如果您正在寻找 优雅 解决方案(简短、易于理解),那么您喜欢的是一个很好的起点。为了正确起见,您应该将 decimal.Decimal(str(number)) 替换为从数字本身创建小数,这将为您提供其精确表示的小数版本:

d = Decimal(number).quantize(...)...

Decimal(str(number)) 有效舍入 两次 ,因为将浮点数格式化为字符串表示形式会执行其自己的舍入。这是因为 str(float value) 不会尝试打印浮点数的完整十进制表示,它只会打印足够的数字以确保如果将这些确切的数字传递给 float 则得到相同的浮点数构造函数。

如果你想保留正确的舍入,但又要避免依赖大而复杂的decimal模块,当然可以,但你仍然需要一些 实现正确舍入所需的精确算法的方法。例如,您可以使用 fractions:

import fractions, math

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    number_exact = abs(fractions.Fraction(number))
    shifted = number_exact * 10**dec_places
    shifted_trunc = int(shifted)
    if shifted - shifted_trunc >= fractions.Fraction(1, 2):
        result = (shifted_trunc + 1) / 10**dec_places
    else:
        result = shifted_trunc / 10**dec_places
    return sign * float(result)

assert round_half_up(1.49) == 1
assert round_half_up(1.5) == 2
assert round_half_up(1.51) == 2
assert round_half_up(2.49) == 2
assert round_half_up(2.5) == 3
assert round_half_up(2.51) == 3

请注意,上面代码中唯一棘手的部分是将浮点数精确转换为分数,这可以卸载到 as_integer_ratio() float 方法,这就是两个小数和分数在内部做。所以如果你真的想去除对fractions的依赖,你可以将小数运算简化为纯整数运算;你以牺牲一些易读性为代价保持在同一行数内:

def round_half_up(number, dec_places=0):
    sign = math.copysign(1, number)
    exact = abs(number).as_integer_ratio()
    shifted = (exact[0] * 10**dec_places), exact[1]
    shifted_trunc = shifted[0] // shifted[1]
    difference = (shifted[0] - shifted_trunc * shifted[1]), shifted[1]
    if difference[0] * 2 >= difference[1]:  # difference >= 1/2
        shifted_trunc += 1
    return sign * (shifted_trunc / 10**dec_places)

请注意,测试这些函数会突出显示在创建浮点数时执行的近似值。比如print(round_half_up(2.175, 2))打印2.17,因为十进制数2.175不能用二进制精确表示,所以用恰好比小数2.175略小的近似值代替。该函数接收该值,发现它小于对应于小数点 2.175 的实际分数,并决定将其舍入 向下 。这不是实现的怪癖;该行为源自浮点数的属性,也存在于 Python 3 and 2.

round 内置函数中

I don't like it, that I have to convert float to a string (to avoid floating point inaccuracy) and then work with the decimal module. Do you have any better solutions?

是;如果您需要准确表示诸如 2.675 之类的数字并将它们四舍五入为 2.68 而不是 2.67,请在整个程序中使用 Decimal 来表示您的数字。

没有别的办法。在屏幕上显示为 2.675 的浮点数是 而不是 实数 2.675;事实上,它略小于 2.675,这就是为什么它会四舍五入为 2.67:

>>> 2.675 - 2
0.6749999999999998

它仅以字符串形式显示为'2.675',因为它恰好是满足float(s) == 2.6749999999999998的最短字符串。请注意,这种较长的表示形式(有很多 9)也不准确。

无论您如何编写舍入函数,my_round(2.675, 2) 都不可能向上舍入为 2.68my_round(2 + 0.6749999999999998, 2) 也无法向下舍入为 2.67;因为输入实际上是相同的浮点数。

因此,如果您的数字 2.675 被转换为浮点数并再次转换回浮点数,那么您已经失去了关于它是应该向上舍入还是向下舍入的信息。解决办法是一开始就不要让它浮动。