请考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些不准确之处?
二进制浮点数学是这样的。在大多数编程语言中,它基于IEEE 754 标准 。 JavaScript 使用 64 位浮点表示,这与 Java 的double
表示相同。问题的关键在于数字以这种格式表示为 2 的幂的整数倍; 有理数(例如0.1
,即1/10
),其分母不是 2 的幂,不能准确表示。
对于标准binary64
格式的0.1
,表示可以完全写为
0.1000000000000000055511151231257827021181583404541015625
十进制,或0x1.999999999999ap-4
。 相反,有理数0.1
,即1/10
,可以完全按照
0.1
或十进制,或0x1.99999999999999...p-4
,类似于 C99 hexfloat 表示法,其中...
表示 9 的无限序列。 程序中的常数0.2
和0.3
也将近似于它们的真实值。碰巧最接近0.2
double
大于有理数0.2
但是最接近0.3
double
小于有理数0.3
。 0.1
和0.2
的总和大于有理数0.3
,因此不同意代码中的常量。
对浮点运算问题的一个相当全面的处理是每个计算机科学家应该知道的浮点运算 。有关更易于理解的解释,请参阅floating-point-gui.de 。
我相信自从我设计和构建浮点硬件以来,我应该为此添加一个硬件设计师的视角。了解错误的起源可能有助于理解软件中发生的事情,最终,我希望这有助于解释为什么浮点错误发生并且似乎随着时间的推移而积累的原因。
从工程角度来看,大多数浮点运算都会有一些错误因素,因为执行浮点计算的硬件只需要在最后一个位置的误差小于一个单位的一半。因此,对于单个操作而言 ,许多硬件将停止在仅产生小于一个单元的一半的误差的精度,这在浮点除法中尤其成问题。单个操作的构成取决于该单元占用的操作数。对于大多数情况,它是两个,但有些单位需要 3 个或更多操作数。因此,不能保证重复操作会导致所需的错误,因为错误会随着时间的推移而增加。
大多数处理器遵循IEEE-754标准,但有些使用非规范化或不同标准。例如,在 IEEE-754 中存在非规范化模式,其允许以精度为代价来表示非常小的浮点数。然而,以下内容将涵盖 IEEE-754 的标准化模式,这是典型的操作模式。
在 IEEE-754 标准中,只要硬件设计者在最后一个地方不到一个单位的一半,就允许任何错误 / epsilon 值,并且结果只需要小于最后一个单位的一半。一次操作的地方。这解释了为什么当重复操作时,错误加起来。对于 IEEE-754 双精度,这是第 54 位,因为 53 位用于表示浮点数的数字部分(标准化),也称为尾数(例如 5.3e5 中的 5.3)。接下来的部分将详细介绍各种浮点运算的硬件错误原因。
浮点除法误差的主要原因是用于计算商的除法算法。大多数计算机系统使用乘法乘法来计算除法,主要在Z=X/Y
, Z = X * (1/Y)
。迭代地计算除法,即每个周期计算商的一些比特直到达到期望的精度,对于 IEEE-754,在最后的位置具有小于一个单位的误差。 Y(1 / Y)的倒数表被称为慢除法中的商选择表(QST),商选择表的位大小通常是基数的宽度,或者是位数的比特数。在每次迭代中计算的商,加上一些保护位。对于 IEEE-754 标准,双精度(64 位),它将是分频器的基数的大小,加上一些保护位 k,其中k>=2
。因此,例如,用于一次计算 2 位商(分数 4)的除法器的典型商数选择表将是2+2= 4
位(加上几个可选位)。
3.1 除法舍入误差:倒数近似
商选择表中的倒数取决于划分方法 :诸如 SRT 划分的慢划分,或诸如 Goldschmidt 划分的快速划分; 根据除法算法修改每个条目以试图产生尽可能低的错误。但无论如何,所有倒数都是实际倒数的近似值 ,并引入了一些误差因素。慢速分割和快速分割方法都迭代地计算商,即每一步计算商的一些位数,然后从被除数中减去结果,并且除法器重复这些步骤直到误差小于一半单位在最后一个地方。慢速划分方法在每个步骤中计算商的固定位数,并且通常构建成本较低,并且快速划分方法计算每步的可变位数并且通常构建成本更高。除法方法中最重要的部分是它们中的大多数依赖于倒数的近似重复乘法,因此它们容易出错。
所有操作中舍入错误的另一个原因是 IEEE-754 允许的最终答案的截断模式不同。有截断,圆向零, 圆到最近(默认),向下舍入和向上舍入。对于单个操作,所有方法在最后位置引入小于一个单元的误差元素。随着时间的推移和重复的操作,截断也会累积地增加结果误差。这种截断误差在求幂中尤其成问题,它涉及某种形式的重复乘法。
由于执行浮点计算的硬件仅需要产生一个结果,错误小于单个操作的最后一个单位的一半,如果没有观察,错误将在重复操作上增加。这就是为什么在需要有界误差的计算中,数学家使用诸如在 IEEE-754 的最后位置使用舍入到最近的偶数位的方法 ,因为随着时间的推移,错误更可能相互抵消 out 和Interval Arithmetic结合IEEE 754 舍入模式的变化来预测舍入误差,并纠正它们。由于与其他舍入模式相比其相对误差较小,因此舍入到最接近的偶数位(在最后一位)是 IEEE-754 的默认舍入模式。
请注意,默认舍入模式, 即最后一个位置的舍入到最接近的偶数位 ,可确保一次操作的最后一个位置的误差小于一个单位的一半。单独使用截断,向上舍入和向下舍入可能会导致错误大于最后一个位置的一个单位的一半,但在最后一个位置时小于一个单位,因此不建议使用这些模式,除非它们是用于区间算术。
简而言之,浮点运算中的错误的根本原因是硬件中的截断和在除法的情况下截断倒数的组合。由于 IEEE-754 标准在单个操作中仅需要小于一个单元的一半的误差,因此除非经过校正,否则重复操作的浮点误差将相加。
当您将. 1 或 1/10 转换为基数 2(二进制)时,您会在小数点后得到重复模式,就像尝试在基数 10 中表示 1/3 一样。值不准确,因此您无法做到使用常规浮点方法精确数学。