浮点数和舍入错误的避免

Floating point and the avoidance of rounding errors

众所周知,浮点数的用户必须注意舍入误差。例如,1.0/3*3 == 1 在几乎所有现代编程语言中的计算结果都是 false。对于非专业人士来说更令人惊讶的是 1.0/10*10 == 1.

但是,浮点系统至少似乎可以更好地处理这些问题。特别是,我在 Apple II 和 Commodore Vic-20 的模拟器中尝试了上述两个测试,并且在每种情况下每个表达式的计算结果都为真。这是违反直觉的:非常原始的系统似乎比更先进的系统运行得更好。

旧的浮点系统是如何在上述测试中得到想要的答案的?假设现代 IEEE 浮点数有充分的理由不这样做,那么它会获得什么回报呢?或者换句话说,是什么问题导致旧的浮点系统被放弃,即使它们能够表示 1/10 和 1/3 而没有最麻烦的舍入错误?

编辑:Simon Byrne 正确指出了我在上面列出的确切测试,确实传递了 IEEE 浮点数。我不知道我犯了什么错误,无法重现。但这里有一个失败了,刚才在 Python 中尝试过:

>>> 0.1+0.1+0.1 == 0.3
False

那个准确的测试在 Apple II 上成功了,那么旧系统是如何得到那个结果的,权衡是什么?

在数学上,不可能找到一个可以精确表示所有分数的数系基数,因为素数有无穷多个。如果要精确存储分数,则必须将分子和分母分别存储,这会使计算更加复杂。

如果将分数存储为单个值,某些操作会引入小错误。如果重复执行,错误会累积并变得明显。

有两种方法可以解决这个问题:

  • 如果您能找到一个公分母,请将所有值按此缩放并使用整数。例如,使用整数美分而不是浮点数美元。

  • 在适当的时候对数字进行四舍五入。很多时候,打印比最大精度小一位或两位数的浮点数就足够了。

不能像整数那样测试浮点数是否相等。有点像数两组人,测试两组人数是否相同,测试两瓶牛奶的量是否相同。
对于 "milk" 测试,您必须说明金额可能有多少差异仍被视为 "equal"。

Apple II 没有浮点硬件,允许浮点计算的是它的 BASIC。我猜他们为相等性测试包含了这样一个错误界限,或者他们使用了以 10 为基数的数字(BCD,请参阅哈罗德的评论)。

我的猜测是,他们可能只是因为您碰巧选择的特定示例而走运。例如,在 IEEE754 binary32 算术中语句为真:

>>> import numpy as np
>>> np.float32(0.1) + np.float32(0.1) + np.float32(0.1) == np.float32(0.3)
True

基于this posting,Apple II 不提供硬件浮点,因此具体细节取决于所提供的软件(听起来好像不同的软件提供不同的实现)。如果他们碰巧使用相同的 24 位有效数字(或给出相似结果的另一个有效数字),那么您会看到相同的答案。

更新:this document 似乎表明 Applesoft Basic 确实使用了 24 位有效数(不是 25 – 24 加上一个隐含的 1 – 正如之前的 link 似乎暗示的那样),这将解释为什么你看到与 binary32 算术相同的结果。