Python 3.x四舍五入

Ice*_*Hay 8 python rounding python-3.x

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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

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)
Run Code Online (Sandbox Code Playgroud)

(与上面的代码不同,只是将浮点数直接转换为十进制数而不是首先不转换为字符串)在这样使用时返回2.18:round_half_up(2.175, 2)但这不是因为以浮点数的方式Decimal(2.175)返回Decimal('2.17499999999999982236431605997495353221893310546875')数字由计算机表示。出人意料的是,第一个代码返回2.18,因为浮点数先转换为字符串。似乎str()函数对最初要舍入的数字进行了隐式舍入。因此,发生了两次取整。即使这是我所期望的结果,但从技术上讲还是错误的。

use*_*342 7

舍入很难做到正确,因为您必须非常小心地处理浮点计算。如果您正在寻找一个优雅的解决方案(简短,易于理解),那么您所喜欢的东西就是一个很好的起点。正确地说,您应该替换decimal.Decimal(str(number))为根据数字本身创建小数,这将为您提供其确切表示形式的小数形式:

d = Decimal(number).quantize(...)...
Run Code Online (Sandbox Code Playgroud)

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

如果您想保留正确的舍入,但又避免依赖大型复杂decimal模块,则可以这样做,但是您仍然需要某种方法来实现正确舍入所需的精确算术。例如,您可以使用分数

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
Run Code Online (Sandbox Code Playgroud)

请注意,上述代码中唯一棘手的部分是将浮点数精确转换为分数,并且可以将其卸载到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)
Run Code Online (Sandbox Code Playgroud)

请注意,测试这些功能可让您重点了解创建浮点数时执行的近似处理。例如,进行print(round_half_up(2.175, 2))打印,2.17因为十进制数字2.175不能精确地用二进制表示,因此它被近似小于2.175十进制的近似值代替。函数接收该值,发现它比相应于2.175的小数实际分数小,并决定圆它下来。这不是实现的怪癖;该行为源自浮点数的属性,并且还存在于roundPython 32的内置函数中。