浮点数不是真实的
数学角度中,浮点数并不是“实数”,尽管它们在某些编程语言中叫实型数据,比如Pascal和Fortran。实数有着无限的精度因而是持续的、无损的;浮点数只有有限的精度,所以它们是有限的,像是“表现异常”的整数,因为他们没有在范围内均匀分布。
举个例子,分配2147483647(最大的32位有符号整数)给一个32位float变量(记为x),打印它,你会看到2147483648。再打印x - 64,仍是2147483648。然后打印x - 65,你会得到2147483520!为什么?因为在那个范围的相邻浮点数之间的间隔是128,而且浮点操作会近似到(类似四舍五入)最近的浮点数。
IEEE浮点数精度是固定的,基于以2为底的科学计数法:1.dd2...dp-1× 2e,其中p是精度(float是24,double是53)。连续的两个数字之间的间隔是21-p+e,可以安全地近似为 ε|x|,其中ε是机器精度(21-p)。
了解了相邻浮点数之间的间隔,可以帮助你避免经典的数字错误。比如,你正在进行迭代计算,例如查找方程根,要求比数字系统能给的相邻结果的精度更高的精度,是没道理的。确保你要求的宽度差不少于相邻数字的间隔,否则就死循环了。
既然浮点是实数的近似,那么免不了会有点误差表现。这个误差,即所谓的舍入误差,可能导致令人惊讶的结果。如果你把相近的数字相减,比如,最高有效数字抵消了,最低有效位(舍入误差存在的位)在浮点数结果中升到了最高有效位,任何相关的计数基本上都被破坏了(这就是称为“抹除”,smearing的现象)。你需要在算法中仔细检查以避免此类的巨量消失。举例来说,考虑使用二次公式解方程 x2 - 1000x + 1 =0。由于表达式 -b + sqrt(b2 -4) 中各操作数的量值几乎相等,你可以计算 r1 = -b - sqrt(b2 - 4),然后代入 r2 = 1/r1,因为对于二次方程 ax2 + bx + c = 0,两个根满足 r1r2 = c/a。
抹除可能以更加微妙的方式发生。假设一个库通过公式1 + x + x2/2 + x3/3! + .... 天真地计算ex。这对正数x是有效的,但是想一下x是很大的负数时会发生什么。偶数次的条目会产生巨大的正数,减去奇数次的量值不会影响结果。这里的问题是大的正数条目的舍入误差在一个比真正答案更高的有效位上。结果会发散到正无穷!解决方法也很简单,对于负数x,计算ex = 1/e|x|。
不言而喻,你不应该在金融应用程序中用浮点数——那正是Python、C#等语言中的小数型的用处。浮点数的目的是高效的科学计算,但是不精确的话,高效也没有意义,所以请记住舍入误差的根源并相应地编码!
原文:Floating-point Numbers Aren't Real byChuck Allison