提问者:小点点

浮点数学坏了吗?


请考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些不准确的情况?


共3个答案

匿名用户

二进制浮点数学是这样的。 在大多数编程语言中,它是基于IEEE 754标准的。 问题的关键在于,数字在这种格式中被表示为整数乘以2的次方; 分母不是二的幂的有理数(如0.1,即1/10)不能精确表示。

对于标准binary64格式的0.1,表示形式可以完全写成

  • 0.100000000000000000055511151231257827021181583404541015625十进制,或
  • 0x1.99999999999999AP-4,采用C99六浮点表示法。

相反,有理数0.1(即1/10)可以精确地写成

  • 0.1十进制,或
  • 0x1.999999999999999999999999P4,类似于C99六浮点表示法,其中...表示9的无尾序列。

程序中的常量0.20.3也是其真实值的近似值。 与0.2最接近的double比有理数0.2大,而与0.3最接近的double比有理数0.3小。 0.10.2之和最终大于有理数0.3,因此与代码中的常量不一致。

一个相当全面的处理浮点算术问题是每个计算机科学家应该知道的关于浮点算术。 有关更易于理解的解释,请参见floating-point-gui.de。

附注:所有位置(以N为基数)数系统都存在这个问题

普通的旧十进制(以10为基数)数字也有同样的问题,这就是为什么像1/3这样的数字最后会变成0.333333333.。。

您刚刚偶然发现了一个数字(3/10),这个数字很容易用十进制表示,但不适合二进制。 它也是双向的(在某种程度上):1/16在十进制中是一个丑陋的数字(0.0625),但在二进制中,它看起来就像十进制中的10,000分之一(0.0001)**一样整洁--如果我们在日常生活中习惯使用以2为基数的数字系统,你甚至会看着这个数字,本能地理解你可以通过将某物减半,一次又一次地减半来达到这个目的。

**当然,浮点数在内存中的存储方式并不完全是这样的(它们使用的是一种科学表示法)。 然而,它确实说明了这样一个观点:二进制浮点精度错误往往会出现,因为我们通常感兴趣的“现实世界”数字常常是十的幂--但这仅仅是因为我们每天都使用十进制数字系统。 这也是为什么我们会说71%而不是“每7中的5个”(71%是一个近似值,因为5/7不能用任何十进制数字精确表示)。

所以不是:二进制浮点数并没有被打破,它们只是碰巧和其他每一个base-N数系统一样不完美:)

侧面注意:在编程中使用浮动

实际上,这个精度问题意味着您需要使用舍入函数将浮点数舍入到您感兴趣的小数位数,然后再显示它们。

您还需要用允许一定公差的比较来替换相等性测试,这意味着:

不要执行如果(x==y){。。。}

相反,请执行if(abs(x-y)

其中abs是绝对值。 MyTolerancEvalue需要针对您的特定应用程序进行选择--它将与您准备允许的“回旋余地”有很大关系,以及您将要比较的最大数字可能是多少(由于精度损失问题)。 注意你所选择的语言中的“Epsilon”风格常量。 这些值不能用作公差值。

匿名用户

我相信我应该加入一个硬件设计师的视角,因为我设计和构建浮点硬件。 了解错误的起源可能有助于理解软件中正在发生的事情,最终,我希望这有助于解释为什么浮点错误会发生并且似乎会随着时间积累的原因。

从工程的角度来看,大多数浮点运算都会有一些误差因素,因为执行浮点计算的硬件在最后一个位置只要求具有小于一个单位的二分之一的误差。 因此,许多硬件将停止在一个精度,这只需要在一个单运算的最后位置产生小于一个单位的二分之一的误差,这在浮点除法中尤其成问题。 单个操作的构成取决于该单元所取的操作数的多少。 对于大多数,它是两个,但有些单位需要3个或更多的操作数。 因此,不能保证重复操作会导致期望的错误,因为错误会随着时间的推移而累积。

大多数处理器遵循IEEE-754标准,但有些处理器使用非规范化或不同的标准。 例如,在IEEE-754中存在一种非规范化模式,它允许以牺牲精度为代价来表示非常小的浮点数。 然而,下面将介绍IEEE-754的标准化模式,这是典型的操作模式。

在IEEE-754标准中,硬件设计人员允许任何误差/Epsilon值,只要该值小于最后一个单位的二分之一,并且对于一次操作,结果只需小于最后一个单位的二分之一。 这就解释了为什么当存在重复操作时,错误会叠加起来。 对于IEEE-754双精度,这是第54位,因为53位用于表示浮点数(例如5.3e5中的5.3)的数字部分(归一化),也称为尾数。 接下来的部分将更详细地介绍各种浮点运算中硬件错误的原因。

浮点除法中产生误差的主要原因是计算商所使用的除法算法。 大多数计算机系统使用乘以逆来计算除法,主要是z=X/yz=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的最后位置使用舍入到最近的偶数的方法,因为随着时间的推移,误差更有可能相互抵消,以及区间算术结合IEEE754舍入模式的变化来预测舍入误差并校正它们。 与其他舍入模式相比,舍入到最近的偶数位(在最后一位)是IEEE-754的默认舍入模式,因为其相对误差较低。

请注意,默认舍入模式(最后一位舍入到最近的偶数位)保证一次操作的误差小于最后一位单位的二分之一。 单独使用截断,上舍入和下舍入可能导致误差大于最后一个单位的二分之一,但小于最后一个单位,因此不建议使用这些模式,除非它们用于区间算术。

简单地说,浮点运算中产生错误的根本原因是硬件中的截断和除法中的倒数截断的综合作用。 由于IEEE-754标准只要求一次运算的最后一个位置的误差小于一个单位的二分之一,因此重复运算的浮点误差将会累加,除非纠正。

匿名用户

当你将。1或1/10转换为基数2(二进制)时,你会在小数点后得到一个重复模式,就像试图用基数10表示1/3一样。 该值并不精确,因此您无法使用普通浮点方法对其进行精确的数学运算。