浮点不准确的例子

时间:2010-01-20 10:10:30

标签: floating-point floating-accuracy

如何向仍然认为计算机无限智慧和准确的新手程序员和外行人解释浮点不准确?
你有一个最喜欢的例子或轶事似乎比一个精确但干燥的解释更好地理解这个想法吗? 这是如何在计算机科学课程中教授的?

7 个答案:

答案 0 :(得分:26)

人们基本上有两个陷入浮点数的主要陷阱。

  1. 规模问题。每个FP编号都有一个指数,用于确定数字的整体“比例”,因此您可以表示非常小的值或非常大的值,尽管您可以为此投入的数字位数有限。添加两个不同比例的数字有时会导致较小的一个被“吃掉”,因为无法将其放入更大的比例。

    PS> $a = 1; $b = 0.0000000000000000000000001
    PS> Write-Host a=$a b=$b
    a=1 b=1E-25
    PS> $a + $b
    1
    

    作为这种情况的类比,你可以想象一个大型游泳池和一茶匙水。两者的大小差异很大,但你可以轻松掌握它们的大小。然而,将茶匙倒入游泳池,将使您仍然可以使用大约一个充满水的游泳池。

    (如果学习此问题的人在使用指数表示法时遇到问题,也可以使用值1100000000000000000000左右。)

  2. 然后存在二进制与十进制表示的问题。像0.1这样的数字不能用有限数量的二进制数字来精确表示。但有些语言掩盖了这一点:

    PS> "{0:N50}" -f 0.1
    0.10000000000000000000000000000000000000000000000000
    

    但是你可以通过重复添加数字来“放大”表示错误:

    PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum
    9,99999999999998
    
    但是,我不能想出一个很好的类比来正确解释这一点。基本上同样的问题是为什么你只能用十进制表示 1 / 3 ,因为要得到你需要在小数部分末尾无限重复3的精确值

    类似地,二进制分数适用于表示一半,四分之一,八分之一等。但是像十分之一这样的东西会产生无限重复的二进制数字流。

  3. 然后还有另一个问题,尽管大多数人都不会绊倒,除非他们做了大量的数字化工作。但那时,那些已经知道这个问题的人。由于许多浮点数仅仅是精确值的近似值,这意味着对于实数 r 的给定近似 f ,可以存在无限多个实数 r 1 r 2 ,...映射到完全相同的近似值。这些数字在一定的间隔内。假设 r min r 的最小可能值,导致 f r < / em> max r 的最大可能值,它有一个区间[ r min r max ]其中该间隔中的任何数字都可以是您的实际数字 r

    现在,如果你对这个数字进行计算 - 加法,减法,乘法等 - 就会失去精确度。每个数字只是一个近似值,因此您实际上是使用 interval 执行计算。结果也是间隔,并且近似误差仅变得更大,从而加宽了间隔。您可以从该计算中找回一个数字。但是,这只是可能结果区间内的一个数字,考虑到原始操作数的精度和计算引起的精度损失。

    这种事情被称为Interval arithmetic,至少在我看来它是我们大学数学课程的一部分。

答案 1 :(得分:8)

告诉他们基础10系统完全 同样的问题。

尝试在基数10中将1/3表示为十进制表示。您将无法完全执行此操作。

因此,如果您编写“0.3333”,您将获得许多用例的合理精确表示。

但是如果你把它移回一个分数,你会得到“3333/10000”,与“1/3”相同。

其他分数,例如1/2,很容易用基数为10的有限十进制表示法表示:“0.5”

现在base-2和base-10遭遇的问题基本相同:两者都有一些他们无法准确表示的数字。

虽然base-10没有问题,在base-2中将1/10表示为“0.1”,但你需要一个以“0.000110011 ..”开头的无限表示。

答案 2 :(得分:6)

如何解释外行人的问题。计算机代表数字的一种方法是计算离散单位。这些是数字计算机。对于整数,没有分数部分的那些,现代数字计算机计算两个幂:1,2,4,8 ,,,地方值,二进制数字,等等,等等,等等。对于分数,数字计算机计算两个的反幂:1 / 2,1 / 4,1 / 8,......问题是许多数不能用有限数量的这些反幂的和来表示。使用更多的位置值(更多位)将增加那些“问题”数字的表示的精度,但是从来没有完全得到它,因为它只有有限的位数。有些数字不能用无限多的位表示。

暂停...

好的,你想测量容器中的水量,你只有3个量杯:满杯,半杯和四分之一杯。在计算完最后一个完整的杯子之后,让我们说剩余三分之一的杯子。然而,你无法衡量,因为它并没有完全填充可用杯子的任何组合。它没有填满半杯,四分之一杯的溢出量太小,无法填充任何东西。所以你有一个错误 - 1/3和1/4之间的差异。当您将其与其他测量中的错误结合使用时,此错误会更加复杂。

答案 3 :(得分:2)

在python中:

>>> 1.0 / 10
0.10000000000000001

解释一些分数如何不能用二进制精确表示。就像一些分数(如1/3)无法在基数10中精确表示。

答案 4 :(得分:2)

另一个例子,在C

printf (" %.20f \n", 3.6);

难以置信地给出了

3.60000000000000008882

答案 5 :(得分:1)

这是我的简单理解。

问题: 值0.45无法用浮点数精确表示,并向上舍入到0.450000018。那是为什么?

答案: int值为45由二进制值101101表示。 为了使值为0.45,如果它可以取45 x 10 ^ -2(= 45/10 ^ 2),那将是准确的。 但这是不可能的,因为你必须使用基数2而不是10。

所以最接近10 ^ 2 = 100将是128 = 2 ^ 7。对于值45(101101),您需要的总位数为9:6,对于值7(111),您需要的总位数为3位。 然后值45 x 2 ^ -7 = 0.3515625。现在你有一个严重的不准确问题。 0.3515625差不多接近0.45。

我们如何改善这种不准确性?好吧,我们可以将值45和7更改为其他内容。

460 x 2 ^ -10 = 0.44921875怎么样?你现在使用9位为460和4位为10.然后它更接近但仍然没有那么接近。但是,如果您的初始期望值为0.44921875,那么您将得到完全匹配而没有近似值。

因此,您的值的公式为X = A x 2 ^ B.其中A和B是正数或负数的整数值。 显然,数字越高,您的准确度就越高,但是您知道表示值A和B的位数是有限的。对于float,总数为32. Double为64,Decimal为128。

答案 6 :(得分:0)

如果将9999999.4999999999转换为float并返回double,则可能会出现一个可爱的数字怪异。结果报告为10000000,即使该值明显接近9999999,即使9999999.499999999正确舍入到9999999。