是什么导致浮点舍入错误?


62

我知道浮点运算有精度问题。我通常通过切换到数字的固定十进制表示法或简单地忽略错误来克服它们。

但是,我不知道这种不准确的原因是什么。为什么浮点数会有这么多的舍入问题?


28
确切地说,并不是大多数人担心的舍入引起的错误,而是二进制浮点舍入以不直观的方式表现的事实。切换到十进制表示形式可以使舍入操作更直观,但是作为交换,您几乎总是会增加相对误差(否则必须增加存储空间来进行补偿)。
丹尼尔·普里登

12
我试图消除最常见的困惑:浮点数
gui.de

我认为@DanielPryden的意思是“切换到[定点]表示可以使舍入以更直观的方式表现……”。什么使舍入的问题,无论是固定或浮点数是任一有限的字宽。只是,对于浮点数,舍入误差的大小通常与被舍入的数字的大小大致保持比例。(除非您得到的数字很小且为“非
正规化

@robert:这与我指的不完全相同。大多数人在使用浮点数时遇到的“错误”与浮点数本身无关,这是基础。IEEE-754浮点和双精度使用基数2中的指数,这意味着小数四舍五入为2(1 / 2、1 / 16、1 / 1024等)的负幂,而不是10(1 / 10、1 / 1000等。)这会导致不直观的结果,例如将0.1舍入到0.1000001以及类似的问题。
Daniel Pryden 2015年

您可以在以10为底的数字中做浮点数-这就是.NET decimal类型的工作原理。另一方面,固定点是不同的。只要范围有限,定点就是一个很好的答案。但是,限制范围使定点不适合许多数学应用,因此,在硬件中定点数的实现常常没有得到很好的优化。
Daniel Pryden 2015年

Answers:


82

这是因为某些分数需要非常大(甚至是无限大)的位置来表示而不进行舍入。这对于十进制表示法和对二进制或任何其他表示法一样适用。如果要限制用于计算的小数位数(并避免以分数表示法进行计算),则必须将一个简单的表达式四舍五入为1/3 + 1/3。而不是写2/3,因此您必须写0.33333 + 0.33333 = 0.66666,这与2/3不同。

对于计算机,数字位数受其内存和CPU寄存器的技术特性限制。内部使用的二进制符号增加了更多困难。尽管某些编程语言添加了此功能,但计算机通常无法用分数表示法表示数字,从而可以在一定程度上避免这些问题。

每个计算机科学家都应了解的浮点运算法则


12
发现。但我还要指出,一些以十进制结尾的数字不会以二进制结尾。特别是0.1是二进制的重复数,因此没有浮点二进制数可以精确表示0.1。
杰克·艾德利

4
浮点数不仅对许多小数位有用。32位整数最多只能计数40亿,但是32位浮点数几乎可以无限大。
Abhi Beckert

7
特别是,我们可以表示为有限小数的分数是那些分母的质因子分解仅包含2和5的分数(例如,我们可以表示3/10和7/25,但不表示11/18)。当我们转向二进制时,我们失去了5的因数,因此只能精确地表示二进位有理数(例如1 / 4、3 / 128)。
David Zhang

70

舍入误差主要来自以下事实:所有实数无穷可能无法由计算机有限内存来表示,更不用说一小部分内存(例如单个浮点变量)了,因此存储的许多数字只是...的近似值。它们代表的数字。

由于仅存在有限数量的值而不是近似值,并且在近似值和另一个数之间的任何运算都会导致近似值,因此舍入误差几乎是不可避免的

重要的是要意识到它们何时可能造成问题,采取措施减轻风险


除了大卫·戈德堡的必要什么每台计算机科学家应该知道关于浮点运算(由Sun / Oracle的重新出版的附录中的数值计算指南),这是由提到的Thorsten,在ACCU杂志超负荷运行了一个极好的理查德•哈里斯Richard Harris)关于浮点布鲁斯的系列文章。

该系列开始于

数值计算有很多陷阱。理查德·哈里斯(Richard Harris)开始寻找银弹。

经常会从沉睡中唤醒数字错误的巨龙,但是如果不慎接近,他有时会对粗心的程序员的计算造成灾难性的破坏。

如此之多,以至于有些程序员在IEEE 754浮点运算法则的丛林中碰到了他,建议他们的同伴不要在那片美丽的土地上旅行。

在本系列文章中,我们将探索数值计算的世界,将浮点算术与已提出的一些更安全的替代技术进行对比。我们将了解到,龙的领土确实确实遥不可及,而且如果我们害怕龙的毁灭性关注,总的来说我们必须谨慎行事。

理查德首先解释了有理数,有理数,无理数,代数数和先验数的实数分类法。然后,他继续解释IEEE754表示形式,然后再讨论取消错误和执行顺序问题。

如果您阅读的内容不深,那么您将对与浮点数有关的问题有很好的了解。

如果您想了解更多,他继续

然后,他转向尝试帮助您治愈微积分蓝调

最后但并非最不重要的是

整个系列文章都很值得研究,总共66页,仍然比Goldberg论文的77页还小。

尽管本系列文章涵盖了许多相同的方面,但我发现它比Goldberg的论文更容易理解。在阅读了Richards的较早文章之后,我还发现更容易理解本文的更复杂部分,而在那些较早的文章之后,Richard进入了Goldberg论文未涉及的许多有趣领域。


作为这样说话AK在评论中提到:

由于这些文章的作者,我想提一提,我已经创造了他们的互动版本在我的博客www.thusspakeak.com开始thusspakeak.com/ak/2013/06


1
由于这些文章的作者,我想提一提,我已经创造了他们的互动版本在我的博客www.thusspakeak.com开始thusspakeak.com/ak/2013/06
因此

谢谢@ thusspakea.k。我在回答中添加了注释,这些交互元素很好地工作。
Mark Booth

12

好吧,索斯特滕拥有确定的联系。我会补充:

任何形式的表示形式都会对一些数字产生舍入误差。尝试用IEEE浮点数或小数点表示1/3。两者都无法准确地做到这一点。这已经超出了回答您的问题的范围,但是我已经成功使用了以下经验法则:

  • 将用户输入的值存储为十进制(因为几乎可以肯定他们是以十进制表示形式输入的-很少有用户会使用二进制或十六进制)。这样一来,您始终可以获得准确的用户输入表示形式。
  • 如果必须存储用户输入的分数,请存储分子和分母(也应使用十进制)
  • 如果您的系统具有相同数量的多个计量单位(例如摄氏/华氏度),并且用户可以输入两者,请存储输入的值和输入的单位。请勿尝试将其转换并另存为单一表示,除非您可以做到而不会损失精度/准确性。在所有计算中使用存储的值单位。
  • 将机器生成的值存储在IEEE浮点中(这可以是由电子测量设备生成的数字,例如带有A / D转换器的模拟传感器,或者是未舍入的计算结果)。请注意,如果您正在通过串行连接读取传感器,并且已经以十进制格式(例如18.2 C)为您提供了该值,则该方法将不适用。
  • 用十进制存储用户可见的总计等(例如银行帐户余额)。适当舍入,但将该值用作所有将来计算的确定值。

我会补充:考虑使用诸如ARPREC或decNumber之类的任意精度数学包。
Blrfl 2011年

对于整数值(例如小数的分子和分母),我不认为十进制(相对于二进制)没有太大好处。两者都可以存储精确的整数值,而二进制则更有效。来回转换输入和输出会产生一些成本,但实际执行I / O的成本可能会淹没该成本。
基思·汤普森

10

到目前为止似乎尚未提及的是不稳定算法病态问题的概念。我将首先讨论前者,因为对于新手数字学家来说,这似乎是一个更常见的陷阱。

考虑计算(互惠)黄金分割率的幂φ=0.61803…;一种可行的解决方法是使用φ^n=φ^(n-2)-φ^(n-1)φ^0=1和开头的递归公式φ^1=φ。如果您在自己喜欢的计算环境中运行此递归并将结果与​​经过精确评估的能力进行比较,则会发现重要数字的缓慢消耗。这是在Mathematica中发生的情况:

ph = N[1/GoldenRatio];  
Nest[Append[#1, #1[[-2]] - #1[[-1]]] & , {1, ph}, 50] - ph^Range[0, 51]  
{0., 0., 1.1102230246251565*^-16, -5.551115123125783*^-17, 2.220446049250313*^-16, 
-2.3592239273284576*^-16, 4.85722573273506*^-16, -7.147060721024445*^-16, 
1.2073675392798577*^-15, -1.916869440954372*^-15, 3.1259717037102064*^-15, 
-5.0411064211886014*^-15, 8.16837916750579*^-15, -1.3209051907825398*^-14, 
2.1377864756200182*^-14, -3.458669982359108*^-14, 5.596472721011714*^-14, 
-9.055131861349097*^-14, 1.465160458236081*^-13, -2.370673237795176*^-13, 
3.835834102607072*^-13, -6.206507137114341*^-13, 1.004234127360273*^-12, 
-1.6248848342954435*^-12, 2.6291189633497825*^-12, -4.254003796798193*^-12, 
6.883122762265558*^-12, -1.1137126558640235*^-11, 1.8020249321541067*^-11, 
-2.9157375879969544*^-11, 4.717762520172237*^-11, -7.633500108148015*^-11, 
1.23512626283229*^-10, -1.9984762736468268*^-10, 3.233602536479646*^-10, 
-5.232078810126407*^-10, 8.465681346606119*^-10, -1.3697760156732426*^-9, 
2.216344150333856*^-9, -3.5861201660070964*^-9, 5.802464316340953*^-9, 
-9.388584482348049*^-9, 1.5191048798689004*^-8, -2.457963328103705*^-8, 
3.9770682079726053*^-8, -6.43503153607631*^-8, 1.0412099744048916*^-7, 
-1.6847131280125227*^-7, 2.725923102417414*^-7, -4.4106362304299367*^-7, 
7.136559332847351*^-7, -1.1547195563277288*^-6}

的所谓结果φ^41有错误的符号,甚至更早的时候,的计算值和实际值φ^39也没有共同的数字(3.484899258054952* ^-9 for the computed version against the true value7.071019424062048 *^-9)。因此,该算法不稳定,因此不应在不精确的算法中使用此递归公式。这是由于递归公式的固有性质:此递归有一个“衰减”和“增长”的解决方案,并且当有另一个“增长”解决方案乞求时,尝试通过正向解决方案来计算“衰减”解决方案。对于数字的悲伤。因此,应该确保他/她的数值算法是稳定的。

现在,讨论病态问题的概念:即使可能存在一种稳定的数值方法,也很可能是您的问题无法用算法解决。这是问题本身的问题,而不是解决方法。数值的典型示例是涉及所谓的“希尔伯特矩阵”的线性方程的解:

希尔伯特矩阵

矩阵是病态矩阵的典范示例:尝试求解具有大希尔伯特矩阵的系统可能会返回不准确的解决方案。

这是Mathematica的演示:比较精确算术的结果

Table[LinearSolve[HilbertMatrix[n], HilbertMatrix[n].ConstantArray[1, n]], {n, 2, 12}]
{{1, 1}, {1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 
  1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1,
   1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 
  1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}

和不精确的算术

Table[LinearSolve[N[HilbertMatrix[n]], N[HilbertMatrix[n].ConstantArray[1, n]]], {n, 2, 12}]
{{1., 1.}, {1., 1., 1.}, {1., 1., 1., 1.}, {1., 1., 1., 1., 1.},  
  {1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1.}, 
  {1., 1., 1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1., 1., 1.},  
  {1., 1., 1., 0.99997, 1.00014, 0.999618, 1.00062, 0.9994, 1.00031, 
  0.999931}, {1., 1., 0.999995, 1.00006, 0.999658, 1.00122, 0.997327, 
  1.00367, 0.996932, 1.00143, 0.999717}, {1., 1., 0.999986, 1.00022, 
  0.998241, 1.00831, 0.975462, 1.0466, 0.94311, 1.04312, 0.981529, 
  1.00342}}

(如果您确实在Mathematica中进行了尝试,则会注意到一些错误消息,警告您出现不良情况。)

在这两种情况下,仅仅提高精度是无法治愈的。这只会延迟不可避免的数字侵蚀。

这就是您可能会面临的问题。解决方案可能很困难:首先,要么回到绘图板上,要么遍历期刊/书籍/其他内容,以查找是否有人提出了比您更好的解决方案;第二,您要么放弃,要么将问题重新表述为更容易解决的问题。


我给你留下戴安娜·奥利里的名言:

生活可能会给我们带来一些疾病,但没有充分的理由来解决不稳定的算法。


9

因为不能以2为底数来表示以10为底的十进制数

或换句话说,不能将1/10的分母转换为分数为2的分数(这实际上是浮点数)


11
并非完全正确:0.5和0.25可以以2为底表示。我认为您的意思是“并非所有的10以十进制为单位”。
Scott Whitlock

3
更精确地。并非所有的分数数字都可以使用浮点符号来精确表示(即使用。2和10都存在这个确切的问题)。尝试9*3.3333333以十进制形式进行运算,并将其比较9*3 1/3
马丁·约克

1
这是浮点混淆的最常见来源。.1 + .1 != .2因为使用浮点二进制编码,而不是十进制。
肖恩·麦克米兰

@SeanMcMillan:并且1.0/3.0*3.0 != 1.0,因为使用浮点二进制编码,而不是三进制。
Keith Thompson

8

在数学中,有无限多个有理数。一个32位变量只能有2 32个不同的值,而一个64位变量只能有2 64个值。因此,有许多没有精确表示的有理数。

我们可以提出一些方案,使我们可以完美地表示1/3或1/100。事实证明,对于许多实际目的来说,这不是很有用。有一个大例外:在金融业中,十进制小数通常会弹出。这主要是因为金融本质上是一种人类活动,而不是物质活动。

因此,我们通常选择使用二进制浮点,并舍入无法用二进制表示的任何值。但是在金融领域,有时我们选择十进制浮点数,然后将值四舍五入到最接近的十进制值。


2
甚至更糟的是,尽管无限数量的内存可以表示所有的有理数,但不足以表示实数。更糟糕的是,几乎所有的实数都不是可计算的数。对于有限数量的内存,我们能做的最好的事情就是逼近实数的有限范围子集。
David Hammen

4
@Kevin:您正在谈论可计算的数字,它是实数的很小的子集(度量为零的子集)。
David Hammen

1
+1,代表最基本的解释:您正在尝试用有限的位数表示无限数量的数字。
Raku

1
@DavidHammen:可计算的数字只是实数的很小一部分(度量为零),但是根据定义,您将在程序中使用的每个数字都是可计算的。
基思·汤普森

3
@Giorgio:如果选择正确的表示形式,则2的平方根可以表示为例如字符串"√2"。(我的旧HP-48计算器能够做到这一点,并且对该值进行平方运算就可以得出精确的结果2.0。)对于任何有限表示形式,可表示的实数只有一个可数的无穷大,但是没有计算出的数字不会,原则上可以代表。在实践中,二进制浮点极大地限制了可表示数字的集合,具有相对于符号表示而言极快的速度和较小的存储量的优点。
Keith Thompson

-2

我想到的唯一真正明显的带有浮点数的“舍入问题”是移动平均滤波器:

$$ \ begin {align} y [n]&= \ frac {1} {N} \ sum \ limits_ {i = 0} ^ {N-1} x [ni] \&= y [n-1] + \ frac {1} {N}(x [n]-x [nN])\ \ end {align} $$

为了使这项工作不产生噪音,您需要确保在当前样本中添加的$ x [n] $与将在样本中减去$ N $个样本的$ x [nN] $完全相同。未来。如果不是这样,那么不同之处在于会拖延您的延迟线,并且永远不会出来。这是因为此移动平均滤波器实际上是由IIR构成的,该IIR在$ z = 1 $处具有一个边际稳定的极点,而在其中的零则将其抵消。但是,它是一个积分器,所有被积分但未被完全删除的废话将永远存在于积分器总和中。这是定点与浮点数没有相同问题的地方。


嘿,prog.SE论坛中的$ LaTeX $数学标记不起作用吗???如果没有,那真是la脚。
罗伯特·布里斯托

1
在meta.SO和链接的问题查看此内容
AakashM 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.