Python浮点数舍入误差问题

30

我不知道这是否是一个明显的bug,但在运行用于改变模拟参数的Python脚本时,我发现delta=0.29和delta=0.58的结果缺失了。经过调查,我注意到以下Python代码:

for i_delta in range(0, 101, 1):
  delta = float(i_delta) / 100

  (...)

filename = 'foo' + str(int(delta * 100)) + '.dat'

当delta=0.28和0.29时,生成了相同的文件;delta为0.57和0.58时也是一样的。这是因为Python将float(29)/100返回为0.28999999999999998,而非0.29。但这不是系统性错误,不会发生在每个整数上。因此我创建了以下Python脚本:

import sys

n = int(sys.argv[1])

for i in range(0, n + 1):
  a = int(100 * (float(i) / 100))
  if i != a: print i, a

我无法看出这些存在舍入误差的数字中有任何规律。为什么会发生在这些特定的数字上?


4
这只是IEEE 754浮点数的工作方式。我建议您四舍五入将浮点数转换回整数,而不是简单地截断。 - Steve Howard
1
这不是错误 - 在许多不同的编程语言中都很常见。有一些解决方法,但在这种情况下,最简单的解决方案可能就是在文件名中使用idelta。只需记住,默认情况下idelta不会传递到循环外部。 - Tadeck
2
#StdSOAnswer_1. 这就是浮点数的工作原理。 - S.Lott
@Tadeck 我会说这仍然是一个错误,只是现代计算机科学的普遍问题。 - user3064538
2个回答

36

在发布相同的链接之前,我真的没有看到你发布了同样的文档链接。这只是表明它是一个非常好的参考资料。 - dr jimbob
@jimbob,我在原帖后一分钟添加了链接。这是一个经典,但我没有立刻找到它。 - Mark Ransom
8
对于Python爱好者来说,在Python教程中也有一个更短(且更易读)的章节专门讨论这个问题。 - Tim Pietzcker

22

由于浮点数的特性,它非常出名。

如果您想进行十进制算术而不是浮点运算,有可以实现这一点。

E.g.,

>>> from decimal import Decimal
>>> Decimal(29)/Decimal(100)
Decimal('0.29')
>>> Decimal('0.29')*100
Decimal('29')
>>> int(Decimal('29'))
29

通常情况下,使用十进制可能过于浪费,而且在罕见情况下仍会出现四舍五入的错误,尤其是当数字没有有限的十进制表示时(例如任何分母不为1、2或5的分数 - 十进制基数的因数(10))。 例如:

>>> s = Decimal(7)
>>> Decimal(1)/s/s/s/s/s/s/s*s*s*s*s*s*s*s
Decimal('0.9999999999999999999999999996')
>>> int(Decimal('0.9999999999999999999999999996'))
0

因此,在将浮点数转换为整数之前,最好总是四舍五入,除非你需要一个向下取整的函数。

>>> int(1.9999)
1
>>> int(round(1.999))
2

另一种选择是使用fractions库中的分数类,它不会进行近似计算(仅在必要时添加/减去和乘以整数分子和分母)。


嗯,实际上一个更好的例子是Decimal(1)/Decimal(3) * Decimal(3),它不能以更高的精度产生1.0。"当基数不是10"应该是当小数在基数10下无法准确表示时。这个数字当然是十进制的。 - Derek Litz
@DerekLitz - 同意,我的回答有些草率。你的例子更简洁(虽然两者都是有效的)。应该写成当数字在十进制中没有有限小数表示时,这将发生在任何分数的分母不可被2或5整除时。(当然,“无法在十进制中准确地表示分数。当分母不能被2或5整除时,数字当然是以十进制表示的。”也不完全正确。数字没有基数。1/3 = 1 /(1+1+1)无论在哪个进制下都是准确的。写成分数形式后,它可以用十进制表示-1/3。) - dr jimbob
@dr_jimbob 我喜欢上面的改进,但是我不喜欢“数字没有基数”的说法。也许这只是语义上的差异,但是词语的意义很重要。一个数字应该代表一个值(或者如果你更喜欢的话,是数量)。为了创建一个计数系统,需要选择一个基数,选择符号,然后我们就可以比简单的计数更有效地进行沟通了,但我相信这就是你的意思 :) - Derek Litz
@DerekLitz - 是的,数字代表值,但只有数字的表示具有基数。 一、二、二十八、三分之二、π都是数字。 十进制表示分别为:1、2、28、1.5、3.14159...(十进制意味着基数为10),是的,数字名称通常与10进制有关。 在二进制(基数为2)中,它们将变成1、10、11100、1.1、11.0010 0100 0011 1111...,在十六进制中则为1、2、1c、1.8、3.243f... 数字具有特定的数学含义,指抽象对象(例如,数字2是零的第二个后继者:two = succ(succ zero)),与基数无关。 - dr jimbob
@DerekLitz - 我完全承认,这只是无端挑剔。我们所说的意思非常明显。 - dr jimbob
1
@dr_jimbob 我喜欢这些对话 :). 这更多是有关“数字”定义上的模糊,它可以表示代表数学值的抽象概念或者数学值本身。当我周围有数学类型的人时,我应该假设是后者,这是非常重要的知识 :) - Derek Litz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接