29

您如何向仍然认为计算机无限聪明和准确的新程序员和外行解释浮点不准确性?
你有没有一个最喜欢的例子或轶事似乎比精确但枯燥的解释更能传达这个想法?
这是如何在计算机科学课程中教授的?

4

7 回答 7

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 1r 2,...映射到完全相同的近似. 这些数字位于某个区间内。假设r min是导致fr max的最大可能值的r的最小可能值r,那么你得到一个区间 [ r min , r max ],其中该区间中的任何数字都可以是你的实际数字r

    现在,如果你对那个数字进行计算——加、减、乘等——你会失去精度。每个数字都只是一个近似值,因此您实际上是在使用区间执行计算。结果也是一个区间,并且近似误差只会变得更大,从而扩大了区间。您可能会从该计算中得到一个数字。但这只是可能结果区间中的一个数字,考虑到原始操作数的精度和计算导致的精度损失。

    那种东西叫做区间算术,至少对我来说它是我们大学数学课程的一部分。

于 2010-01-20T10:12:17.460 回答
8

向他们展示 base-10 系统也存在完全相同的问题。

尝试将 1/3 表示为以 10 为底的十进制表示。您将无法准确地做到这一点。

因此,如果您编写“0.3333”,您将对许多用例有相当准确的表示。

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

其他分数,例如 1/2 可以很容易地用以 10 为底的有限十进制表示形式表示:“0.5”

现在以 2 为底和以 10 为底的问题基本相同:两者都有一些无法准确表示的数字。

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

于 2010-01-20T12:23:51.110 回答
6

这是如何向外行解释的。计算机表示数字的一种方法是计算离散单位。这些是数字计算机。对于没有小数部分的整数,现代数字计算机计算 2 的幂:1、2、4、8。、、、位值、二进制数字、等等、等等。对于分数,数字计算机计算 2 的逆幂:1/2、1/4、1/8,... 问题是许多数字不能用这些逆幂的有限个数的总和来表示。使用更多位值(更多位)将提高这些“问题”数字表示的精度,但永远不能准确地得到它,因为它只有有限数量的位。有些数字不能用无限位数表示。

打盹...

好的,您想测量容器中水的体积,而您只有 3 个量杯:满杯、半杯和四分之一杯。数完最后一个满杯后,假设还剩三分之一的杯子。然而你无法测量它,因为它不能完全填满可用杯子的任何组合。它没有填满半杯,四分之一杯的溢出太小,无法装满任何东西。所以你有一个错误 - 1/3和1/4之间的差异。当您将它与其他测量的误差结合起来时,这个误差会更加复杂。

于 2010-01-20T12:13:10.667 回答
2

在蟒蛇中:

>>> 1.0 / 10
0.10000000000000001

解释一些分数如何不能用二进制精确表示。就像某些分数(如 1/3)不能以 10 为底精确表示。

于 2010-01-20T10:14:48.317 回答
2

另一个例子,在 C

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

难以置信地给

3.60000000000000008882

于 2011-05-13T07:46:40.803 回答
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。您需要的总位数为 9 :值 45 (101101) 的 6 + 值 7 (111) 的 3 位。那么值 45 x 2^-7 = 0.3515625。现在你有一个严重的不准确问题。0.3515625 并不接近 0.45。

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

460 x 2^-10 = 0.44921875 怎么样。您现在使用 460 的 9 位和 10 的 4 位。然后它有点接近但仍然没有那么接近。但是,如果您的初始期望值是 0.44921875,那么您将获得没有近似值的精确匹配。

所以你的价值的公式是X = A x 2 ^ B。其中 A 和 B 是正整数或负整数。显然,数字越高,您的准确性就会越高,但是因为您知道表示值 A 和 B 的位数是有限的。对于浮点数,总数为 32。双精度数为 64,十进制数为 128。

于 2013-04-16T08:01:51.533 回答
0

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

于 2013-02-05T03:02:02.177 回答