译自: Posted on 6 April 2009 by John

  “有漏洞的抽象”()一词源自Joel Spolsky,他用这个术语表示编程时既能让人免受凌乱的细节所扰,但有时也会把事情弄砸的那些概念。完美的抽象是一个你永远不需要打开的黑盒子。有漏洞的抽象则如同一个偶尔不得不打开一下的黑盒子。

  作为对实数的计算机表示,浮点数就是有漏洞的抽象。通常它们表现得很不错:你甚至可以假装把浮点类型看成数学上的实数。但是当这个有漏洞的抽象“露馅”的时候你却不能这样,虽然这种情况不是很常见。

  我听到过的最多的关于机器数的局限性的解释虽看起来一本正经:“由于只有有限个浮点数,因此它们不能很好地表示实数”但这话基本没什么用!它无法解释为什么在大多数应用中浮点数确实相当好地表示了实数,也没能对这个有漏洞的抽象可能会“露馅”给予任何提示。

  标准浮点数大致有十进制下16位的精度,最大值为10308次方的数量级,即1后面有308个0。(依据IEEE 754标准实现的典型浮点数。)

  十进制16位已经相当不少了。几乎没有任何可测量的量能接近那么大的精度。例如,牛顿万有引力定律中的常数仅仅已知6位有效数字。电子的电荷已知11个有效数字,虽然比牛顿的万有引力常数精度高得多,但仍远低于浮点数。那么何时16位有效数字不够呢?一个产生问题的地方就是减法。其他的基本运算——加法、乘法、除法都非常精确。只要你不上溢或或下溢,这些操作通常会得到直到最后一位都是正确的结果。但减法得到的却可能是从精确到完全不准确之间的任一种结果。如果两个数的n位有效数字相一致,最坏的情况下其减法可能会使n有效数字的精度全部丧失殆尽。这个问题可能出乎预料地在其他计算中间出现。从关于计算标准偏差()的这个帖中既可窥其一斑。在"浮点编程的5个技巧"()帖子的导数计算部分,还有另外一个例子。

  上溢或下溢呢?你什么时候会需要比10308还大的数?通常你是不会需要的。但在诸如概率之类的计算中,除非你足够聪明,否则你始终需要它们。在概率计算中,计算一个天文数字般巨大的数与一个极其极其小的数相乘而得到一个普通大小的乘积是件很稀松平常的事。最终的结果适合计算机(表示)没什么问题,但计算过程中的数可能因为上溢或下溢而不适合计算机的表示。例如大多数计算机中最大的浮点数在170的阶乘与171的阶乘之间。如此巨大的阶乘在许多应用程序中极为平常,常出现在与其他大阶乘的比值当中。参见关于如何计算那些直接计算会导致溢出的阶乘的技巧这篇帖子——"避免上溢、下溢及精度损失"()。

  一般情况下,你能承受对浮点算术细节的那种幸福的无知,但有时你不能。如果想了解更多,David Goldberg的论文“计算机科学家应当知道的浮点算术”()当推首选。

更新:见后续帖子,浮点数剖析()

相关帖子

怎样计算二项式概率()

NaN, 1.#IND, 1.#INF, 及诸如此类的数()