考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
二进制浮点数学就是这样。在大多数编程语言中,它是基于IEEE 754标准的。问题的症结在于数字以这种格式表示为整数乘以2的幂。分母不是2的幂的有理数(例如0.1
,是1/10
)无法精确表示。
对于0.1
标准binary64
格式,表示形式可以完全按照
0.1000000000000000055511151231257827021181583404541015625
以十进制表示,或0x1.999999999999ap-4
以C99十六进制表示法表示。相比之下,合理数量0.1
,这是1/10
可以完全按照书面
0.1
以十进制表示,或0x1.99999999999999...p-4
以C99十六进制表示法的类似形式表示,其中...
表示9的无休止序列。常量0.2
和0.3
程序中的常量也将接近其真实值。碰巧的是,最近double
以0.2
低于合理数量较大0.2
但最近double
以0.3
低于合理数量较小0.3
。的总和0.1
和0.2
卷起比有理数较大0.3
,并因此与在代码中不同意恒定。
每个计算机科学家都应该对浮点算术了解什么是对浮点算术问题的相当全面的处理。有关更容易理解的说明,请参见float-point-gui.de。
旁注:所有位置(以N为底的)数字系统均会精确地共享此问题
普通的旧十进制数(以10为底)有相同的问题,这就是为什么像1/3这样的数字最终会变成0.333333333 ...
您刚刚偶然发现了一个数字(3/10),该数字很容易用十进制表示,但不适合二进制。它也是双向的(在某种程度上):1/16是一个丑陋的数字,十进制(0.0625),但是在二进制中,它看起来像10,000十进制(0.0001)**一样整洁-如果我们在习惯于在我们的日常生活中使用基数2的数字系统,您甚至会查看该数字,并本能地理解将某物减半,一次又一次减半可以到达那里。
**当然,这并不完全是将浮点数存储在内存中的方式(它们使用科学计数形式)。但是,它确实说明了二进制浮点精度误差趋于增加的观点,因为我们通常感兴趣的“真实世界”数字通常是10的幂-但这仅仅是因为我们使用了十进制数天-今天。这也是为什么我们要说71%而不是“每7个中有5个”(71%是一个近似值,因为5/7不能用任何十进制数字精确表示)的原因。
所以不能:二进制浮点数没有坏,它们恰好与其他所有基数N的系统一样不完美:)
侧面说明:在编程中使用浮点数
实际上,这种精度问题意味着您需要使用舍入函数将浮点数四舍五入为您感兴趣的任意小数位,然后再显示它们。
您还需要用允许一定程度的容忍的比较替换相等性测试,这意味着:
千万不能做if (x == y) { ... }
相反if (abs(x - y) < myToleranceValue) { ... }
。
abs
绝对值在哪里。myToleranceValue
需要为您的特定应用选择-这与您准备允许多少“摆动空间”以及要比较的最大数字有很大关系(由于精度问题) )。当心所选语言中的“ epsilon”样式常量。这些不得用作公差值。
我认为“某些错误常量”比“ Epsilon”更正确,因为没有在所有情况下都可以使用的“ Epsilon”。在不同情况下需要使用不同的ε。机器epsilon几乎永远不是一个好常数。
这不是很不错,所有的浮点运算是基于IEEE [754]标准。例如,仍然有一些使用旧的IBM十六进制FP的系统,并且仍然有不支持IEEE-754算术的图形卡。但是,以合理的近似是正确的。
Cray放弃了IEEE-754的速度要求。Java也放松了对优化的坚持。
我认为您应该在此答案中添加一些内容,以说明货币应该始终,始终使用定点算术对整数进行计算,因为货币是量化的。(在内部会计计算中只占很小的几分之一,或者无论您使用的最小货币单位是多少,这都是有道理的-这通常有助于例如在将“每月29.99美元”转换为每日汇率时减少舍入误差-但它应该仍然是定点算法。)
有趣的事实:这个0.1不能准确地用二进制浮点表示,导致臭名昭著的爱国者导弹软件漏洞,导致第一次伊拉克战争期间有28人丧生。