我知道关于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)

我不喜欢它,我必须将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)

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

最佳答案

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

d = Decimal(number).quantize(...)...
Decimal(str(number))有效舍入两次,因为将float格式化为字符串表示形式会执行其自身的舍入。这是因为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的32内置的round中。

关于Python 3.x四舍五入,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58766074/

10-16 07:46