首页 > 解决方案 > 整数转换的舍入是否有不好的副作用?

问题描述

正如您在此处看到的,Python(和 Java 等)中的舍入不应该轻率地完成。

如果你想像在学校学习的那样四舍五入,你不应该这样做:

>>> round(20.5)
20

通常要四舍五入“学校风”,您将使用 Decimal 方法:

>>> import decimal
>>> decimal.Decimal(20.5).quantize(1, rounding=decimal.ROUND_HALF_UP)
Decimal('21')

在我看来,这不是pythonic,我永远无法记住它。

另一种选择是:

>>> int(20.5 + 0.5)
21

如果要在逗号后四舍五入到特定部分,请执行以下操作:

>>> int(20.5555555555 * 1000 + 0.5) / 1000
20.556

这种四舍五入的方式会产生一些不好的副作用吗?

标签: python

解决方案


您所描述的(几乎)是四舍五入策略。但是使用int它不适用于负数:

>>> def round_half_up(x, n=0):
...     shift = 10 ** n
...     return int(x*shift + 0.5) / shift
... 
>>> round_half_up(-1.26, 1)
-1.2

相反,您应该使用math.floor以正确处理负数:

>>> import math
>>> 
>>> def round_half_up(x, n=0):
...     shift = 10 ** n
...     return math.floor(x*shift + 0.5) / shift
... 
>>> round_half_up(-1.26, 1)
-1.3

这种策略的缺点是它往往会扭曲一组数字的统计数据,例如平均值或标准差。假设您收集了一些数字并且所有数字都以.5; 然后将它们四舍五入显然会增加平均值:

>>> numbers = [-3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5]
>>> N = len(numbers)
>>> sum(numbers) / N
0.0
>>> sum(round_half_up(x) for x in numbers) / N
0.5

如果我们使用round half to even的策略,这将导致一些数字被四舍五入,而其他数字被四舍五入,从而相互补偿:

>>> sum(round(x) for x in numbers) / N
0.0

如您所见,例如,平均值保持不变。

这当然只有在数字均匀分布的情况下才有效。如果有偏爱表格数字的趋势,odd + 0.5那么这个策略也不会阻止偏见:

>>> numbers = [i + 0.5 for i in range(-3, 3, 2)]
>>> N = len(numbers)
>>> sum(numbers) / N
-0.5
>>> sum(round_half_up(x) for x in numbers) / N
0.0
>>> sum(round(x) for x in numbers) / N
0.0

对于这组数字,round有效地进行了“四舍五入”,因此两种方法都存在相同的偏差。

如您所见,四舍五入策略显然会影响一些统计数据的偏差,例如平均值。“从一半到偶数”倾向于消除这种偏差,但显然更倾向于偶数而不是奇数,因此也会扭曲原始分布。

关于float对象的注释

由于浮点精度有限,这种“四舍五入”算法也可能会产生一些意想不到的惊喜:

>>> round_half_up(-1.225, 2)
-1.23

解释-1.225十进制数,我们希望结果是-1.22相反的。我们得到-1.23是因为中间的浮点数round_half_up它的预期值稍微滑了一点:

>>> f'{-1.225 * 100 + 0.5:.20f}'
'-122.00000000000001421085'

floor'ing 这个数字给了我们-123(而不是-122如果我们以前得到-122.0过)。这是由于浮点错误造成的,并且首先-1.225实际上并没有存储-1.225在内存中,而是存储为一个更小的数字。出于这个原因,使用Decimal是在所有情况下获得正确舍入的唯一方法。


推荐阅读