浮点数精度不准确的例子

29

如何向初学者和普通人解释浮点数不精确性,他们仍然认为计算机是无限聪明和准确的?
你有没有一个特别喜欢的例子或轶事,它比精确但枯燥的解释更能让人理解这个概念?
计算机科学课程中如何教授这方面的知识?


请查看此文章:计算机科学家应该了解的浮点运算知识 - Rubens Farias
1
你可以用这个简单的JavaScript来验证:alert(0.10.110); - user216441
7个回答

26

人们在处理浮点数时通常会遇到两个主要问题。

  1. The problem of scale. Each FP number has an exponent which determines the overall “scale” of the number so you can represent either really small values or really larges ones, though the number of digits you can devote for that is limited. Adding two numbers of different scale will sometimes result in the smaller one being “eaten” since there is no way to fit it into the larger scale.

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

    As an analogy for this case you could picture a large swimming pool and a teaspoon of water. Both are of very different sizes, but individually you can easily grasp how much they roughly are. Pouring the teaspoon into the swimming pool, however, will leave you still with roughly a swimming pool full of water.

    (If the people learning this have trouble with exponential notation, one can also use the values 1 and 100000000000000000000 or so.)

  2. Then there is the problem of binary vs. decimal representation. A number like 0.1 can't be represented exactly with a limited amount of binary digits. Some languages mask this, though:

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

    But you can “amplify” the representation error by repeatedly adding the numbers together:

    PS> $sum = 0; for ($i = 0; $i -lt 100; $i++) { $sum += 0.1 }; $sum
    9,99999999999998
    

    I can't think of a nice analogy to properly explain this, though. It's basically the same problem why you can represent 1/3 only approximately in decimal because to get the exact value you need to repeat the 3 indefinitely at the end of the decimal fraction.

    Similarly, binary fractions are good for representing halves, quarters, eighths, etc. but things like a tenth will yield an infinitely repeating stream of binary digits.

  3. Then there is another problem, though most people don't stumble into that, unless they're doing huge amounts of numerical stuff. But then, those already know about the problem. Since many floating-point numbers are merely approximations of the exact value this means that for a given approximation f of a real number r there can be infinitely many more real numbers r1, r2, ... which map to exactly the same approximation. Those numbers lie in a certain interval. Let's say that rmin is the minimum possible value of r that results in f and rmax the maximum possible value of r for which this holds, then you got an interval [rmin, rmax] where any number in that interval can be your actual number r.

    Now, if you perform calculations on that number—adding, subtracting, multiplying, etc.—you lose precision. Every number is just an approximation, therefore you're actually performing calculations with intervals. The result is an interval too and the approximation error only ever gets larger, thereby widening the interval. You may get back a single number from that calculation. But that's merely one number from the interval of possible results, taking into account precision of your original operands and the precision loss due to the calculation.

    That sort of thing is called Interval arithmetic and at least for me it was part of our math course at the university.


1
嗨,约翰内斯,那确实是一个很好的例子,但它并没有真正告诉人们为什么它不起作用。我想让某人理解失败的原因,而不仅仅是它偶尔会失败的事实。 - David Rutten
1
嗯,除了解释规模问题和二进制与十进制表示问题外,我认为我没有找到更好的方法来告诉人们这个问题 :/。有人可能会使用类似的轶事,比如在游泳池中加入一茶匙水并不会改变我们对其中含量的感知。 - Joey
更具体地说,我在研讨会中遇到的许多人甚至对科学计数法都不太熟悉,因此他们需要付出相当大的精力来理解-4e200、-4e-200、4e-200和4e200之间的差异。 - David Rutten
1
@David:好的,我已经将其纳入答案并进行了一些详细说明。不过,找到合适的类比和易于理解的解释并不容易。 - Joey
我记得在20世纪80年代有一个用于执行数学运算的“区间算术”类型的软件包;它可以保证在一系列计算结束时,结果将在某个范围内。如果将一堆小数字逐个添加到大数字中,可能会发现报告的区间不可接受地大,但如果计算结果为范围(123.291至124.721),则可以确信正确的值在该范围内。我想知道为什么我之后没有看到这样的东西? - supercat
显示剩余2条评论

8
展示给他们看,十进制系统也存在完全相同的问题。
试着在十进制下用小数表示1/3,你无法完全准确地表示它。
因此,如果你写“0.3333”,对于许多用例来说,你将得到一个相当精确的表示。
但是,如果你把它转换回分数,你会得到“3333/10000”,这与“1/3”不同。
其他分数,如1/2,在十进制下可以很容易地用有限的小数表示:“0.5”。
现在,二进制和十进制基本上存在相同的问题:两者都有一些无法准确表示的数字。
虽然十进制可以用“0.1”来表示1/10,但在二进制中,你需要一个以“0.000110011...”开头的无限表示。

6
这是一个面向普通人的解释。计算机表示数字的一种方式是通过计数离散单位,这就是数字计算机。对于整数,也就是没有小数部分的数字,现代数字计算机计算2的幂次:1、2、4、8……位值、二进制位等等。对于分数,数字计算机计算2的倒数幂:1/2、1/4、1/8……问题是许多数字不能用有限数量的这些倒数幂之和来表示。使用更多位值(更多位)将增加这些“问题”数字表示的精度,但永远无法完全准确,因为它只有有限数量的位。一些数字无法用无限数量的位表示。
打哈欠……
好吧,你想测量容器中的水的体积,而你只有三个量杯:满杯、半杯和四分之一杯。在计数最后一个满杯之后,假设还剩下三分之一杯。但你无法测量它,因为它无法恰好填满任何可用的杯子组合。它无法填满半杯,而四分之一杯的溢出量太小,无法填满任何东西。所以你有一个误差——1/3和1/4之间的差异。当你将其与其他测量误差相结合时,这个误差就会被放大。

2

使用Python:

>>> 1.0 / 10
0.10000000000000001

解释一下为什么有些分数无法在二进制中精确表示。就像在十进制中有些分数(比如1/3)无法被精确表示一样。


codeape,我正在寻找比简单列举舍入误差例子更深入的东西。 我希望能够告诉人们为什么这些错误会出现,并使他们理解其背后的原因,而不需要了解IEEE 754规范。 - David Rutten
1
@David:给他们一个浮点数精确的例子,比如多次加0.25。结果将一直精确,直到溢出尾数,因为0.25是1/(2^2)。然后再用0.2尝试同样的事情,你会遇到问题,因为0.2在有限的二进制数中无法表示。 - Joachim Sauer

2
另一个例子,在C语言中。
printf (" %.20f \n", 3.6);

不可思议地给出了

3.60000000000000008882


1

这是我的简单理解。

问题: 值0.45不能被浮点数准确地表示,会四舍五入为0.450000018。为什么会这样?

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

因此,最接近10^2 = 100的值是128 = 2^7。你需要的总位数是9:6位用于值45(101101)+ 3位用于值7(111)。 然后,值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位。

0
如果将9999999.4999999999转换为float,然后再转换为double,就会观察到一个有趣的数字怪异现象。即使该值明显更接近于9999999,而且9999999.499999999正确地四舍五入到9999999,但结果报告为10000000。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接