我知道关于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的3和2内置的round
中。关于Python 3.x四舍五入,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58766074/