浮点数学运算是否被破坏?

考虑以下代码:

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

为什么会出现这些错误?


#1楼

您尝试过胶带解决方案吗?

尝试确定何时发生错误,并使用简短的if语句修复错误,这虽然不是很漂亮,但是对于某些问题,这是唯一的解决方案,这就是其中之一。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

我在c#的一个科学仿真项目中遇到了同样的问题,我可以告诉你,如果您忽略蝴蝶效应,它将变成一条大胖龙,并在a **中咬你**


#2楼

硬件设计师的观点

我相信我应该为此添加硬件设计师的观点,因为我设计并构建了浮点硬件。 知道错误的来源可能有助于理解软件中发生的事情,并且最终,我希望这可以帮助解释为什么会出现浮点错误并随着时间的推移而累积的原因。

1.概述

从工程的角度来看,大多数浮点运算都将具有一定的错误元素,因为进行浮点计算的硬件仅要求最后的误差小于一个单元的一半。 因此,许多硬件将停止在一个精度上,该精度仅对于单次操作在最后一次产生的误差小于一个单元的一半所必需,而这在浮点除法中尤其成问题。 构成单个操作的要素取决于该单元采用的操作数。 对于大多数情况,它是两个,但是某些单位使用3个或更多操作数。 因此,不能保证重复操作会导致理想的错误,因为随着时间的推移这些错误加起来。

2.标准

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

在IEEE-754标准中,只要最后一位小于一个单位的一半,那么硬件设计者就可以允许任何误差/ε值,并且最后的结果必须小于一个单位的一半。一个手术的地方。 这解释了为什么当重复操作时,错误加起来。 对于IEEE-754双精度,这是第54位,因为53位用于表示浮点数的数字部分(规格化),也称为尾数(例如5.3e5中的5.3)。 下一节将更详细地介绍各种浮点操作上的硬件错误原因。

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除法); 根据划分算法修改每个条目,以尝试产生尽可能低的错误。 但是,无论如何,所有倒数都是实际倒数的近似值 ,并且会引入一些误差元素。 慢速除法和快速除法方法都是迭代计算商,即每一步都会计算商的位数,然后从被除数中减去结果,然后除法器重复执行这些步骤,直到误差小于二分之一为止。单位放在最后。 慢除法在每个步骤中计算商的位数固定,并且通常构建成本较低,而快速除法在每步中计算可变数位数,并且通常构建成本较高。 除法中最重要的部分是,大多数方法都依赖于近似的倒数重复进行乘法运算,因此容易出错。

4.其他操作中的舍入错误:截断

所有操作中舍入错误的另一个原因是IEEE-754允许的最终答案截断的不同模式。 有截断,向零舍入,最近舍入(默认),向下舍入和向上舍入。 对于单个操作,所有方法最后都会引入误差小于一单位的元素。 随着时间的流逝和重复的操作,截断还会累积地增加所产生的错误。 截断误差在取幂时尤其成问题,涉及某种形式的重复乘法。

5.重复操作

由于执行浮点计算的硬件仅需要产生一个结果,该结果的单个操作的最后一个位置的误差小于一个单元的一半,因此如果不注意,该误差将随着重复的操作而扩大。 这就是为什么在需要有限误差的计算中,数学家使用诸如 IEEE-754 的最后一位使用四舍五入到最接近的偶数之类的方法的原因,因为随着时间的流逝,误差更可能相互抵消。 间隔算法IEEE 754舍入模式的变体相结合,以预测舍入误差并进行校正。 由于与其他舍入模式相比其相对误差较低,因此舍入到最接近的偶数位(最后一位)是IEEE-754的默认舍入模式。

请注意,默认的舍入模式( 最后一位舍入到最接近的偶数位)保证一次操作的最后一位的误差小于一个单位的一半。 仅使用截断,向​​上舍入和向下舍入可能会导致错误,最后一个位置的误差大于一个单元的一半,但最后一个位置的误差小于一个单元,因此不建议使用这些模式,除非它们是在间隔算术中使用。

6.总结

简而言之,浮点运算错误的根本原因是硬件的截断和除法时的倒数截断的组合。 由于IEEE-754标准在一次操作中只要求最后一个位置的误差小于一个单元的一半,因此,除非纠正,否则重复操作中的浮点错误将加起来。


#3楼

出现这些怪异的数字是因为计算机出于计算目的使用二进制(基数2)数字系统,而我们使用十进制(基数10)。

大多数小数都不能用二进制或十进制或两种形式精确表示。 结果-舍入(但精确)的数字结果。


#4楼

除了其他正确答案外,您可能还需要考虑缩放值,以避免浮点运算出现问题。

例如:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... 代替:

var result = 0.1 + 0.2;     // result === 0.3 returns false

表达式0.1 + 0.2 === 0.3在JavaScript中返回false ,但是幸运的是,浮点数中的整数运算是精确的,因此可以通过缩放避免十进制表示错误。

作为一个实际的例子,为避免浮点问题,其中精度是最重要的,建议1以整数形式表示货币,该货币代表美分的数量: 2550美分而不是25.50美元。


1 Douglas Crockford: JavaScript:优秀部分 :附录A-糟糕的部分(第105页)


#5楼

已经发布了很多很好的答案,但我想再补充一个。

并非所有数字都可以通过浮点数 / 双精度数来表示。例如,在IEEE754浮点标准中,数字“ 0.2”将以单精度表示为“ 0.200000003”。

引擎盖下的实数存储模型将浮点数表示为

在此处输入图片说明

即使你可以键入0.2很容易, FLT_RADIXDBL_RADIX为2; 对于使用FPU且使用“ IEEE二进制浮点算术标准(ISO / IEEE Std 754-1985)”的计算机,不是10。

因此,要准确地表示这些数字有些困难。 即使您明确指定此变量,也无需任何中间计算。


#6楼

这里的大多数答案都是用非常干燥的技术术语来解决这个问题。 我想用普通人能理解的术语来解决这个问题。

想象一下,您正在尝试切比萨饼。 你有一个机器人比萨刀,可以削减比萨正好一半。 它可以将整个披萨减半,也可以将现有的薄片减半,但是无论如何,减半总是精确的。

比萨切刀的动作非常精细,如果从整个比萨开始,则将其减半,然后每次将最小的薄片减半,则可以将薄片减半53次 ,直到薄片即使对于其高精度功能而言也太小。 此时,您不能再将这一薄片减半,而必须按原样包含或排除它。

现在,您如何将所有的切片切成这样的厚度,使它们的总和等于比萨饼的十分之一(0.1)或五分之一(0.2)? 真正考虑一下,然后尝试解决。 如果您手边有神话般的精密披萨切割器,您甚至可以尝试使用真正的披萨。 :-)


大多数有经验的程序员,当然知道真正的答案,这是没有办法拼凑出一个确切的十分之一或五分之一的比萨使用这些片,不管你如何精细切片他们。 您可以做一个非常好的近似值,如果您将0.1的近似值与0.2的近似值相加,您会得到一个非常好的0.3的近似值,但是仍然只是一个近似值。

对于双精度数字(可以使您的比萨饼减半53的精度),立即小于或大于0.1的数字是0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。 后者比前者更接近0.1,因此,在输入为0.1的情况下,数值解析器将偏爱后者。

(这两个数字之间的差是我们必须决定包括的“最小切片”,它会引入向上的偏差,而排除的结果是引入向下的偏差。该最小切片的技术术语是ulp 。)

在0.2的情况下,数字都是相同的,只是放大了2倍。同样,我们支持略高于0.2的值。

请注意,在两种情况下,0.1和0.2的近似值都有轻微的向上偏差。 如果我们添加足够的这些偏差,它们将使数字远离我们想要的值,并且实际上,在0.1 + 0.2的情况下,偏差足够大,以致所得的数字不再是最接近的数字到0.3。

特别地,0.1 + 0.2实际上是0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125,而最接近0.3的数字实际上是0.2999999999999999888977697484484957636833191791575。


PS一些编程语言还提供了比萨饼切割器,可以将切成精确的十分之一 。 尽管这种比萨饼切割器并不常见,但是如果您确实有机会使用它,那么当重要的是要精确获得十分之一或五分之一的切片时,您应该使用它。

(最初发布在Quora上。)


#7楼

一些统计数据与此著名的双精度问题有关。

当以0.1(从0.1到100)的步长将所有值( a + b )相加时,我们有〜15%的机会出现精度误差 。 请注意,该错误可能导致值更大或更小。 这里有些例子:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

当以0.1(从100到0.1)的步长减去所有值( a-b ,其中a> b )时,我们有〜34%的机会出现精度误差 。 这里有些例子:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.0999999999999943
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值