考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
考虑以下代码:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
为什么会出现这些错误?
当前回答
这些奇怪的数字之所以出现,是因为计算机使用二进制(以2为基数)数字系统进行计算,而我们使用十进制(以10为基数)。
大多数分数不能用二进制或十进制或两者精确表示。结果-四舍五入(但精确)的数字结果。
其他回答
浮点舍入错误。由于缺少5的素因子,0.1在基-2中不能像在基-10中那样精确地表示。正如1/3以十进制表示需要无限位数,但以3为基数表示为“0.1”,0.1以2为基数表示,而以10为基数不表示。计算机没有无限的内存。
浮点舍入错误。从每个计算机科学家应该知道的浮点运算:
将无限多的实数压缩成有限位数需要近似表示。虽然有无限多的整数,但在大多数程序中,整数计算的结果可以存储在32位中。相反,给定任何固定位数,大多数使用实数的计算将产生无法使用那么多位数精确表示的量。因此,浮点计算的结果必须经常舍入,以适应其有限表示。这种舍入误差是浮点计算的特征。
不,不破,但大多数小数必须近似
总结
浮点运算是精确的,不幸的是,它与我们通常的以10为基数的数字表示法不太匹配,所以我们经常给它的输入与我们写的略有不同。
即使是像0.01、0.02、0.03、0.04…0.24这样的简单数字也不能精确地表示为二进制分数。如果你数到0.01、.02、.03…,直到你数到0.25,你才能得到以2为底的第一个分数。如果你尝试使用FP,那么你的0.01会稍微有点偏差,所以要将其中的25个相加到一个精确的0.25,就需要一长串的因果关系,包括保护位和舍入。很难预测,所以我们举手说“FP不准确”,但事实并非如此。
我们不断地给FP硬件一些在基数10中看似简单但在基数2中却是重复的分数。
这是怎么发生的?
当我们用十进制书写时,每个分数(特别是每个终止的小数)都是形式的有理数
a/(2n x 5m)
在二进制中,我们只得到2n项,即:
a/2n
所以在十进制中,我们不能表示1/3。因为基数10包括2作为素因子,所以我们可以写成二进制分数的每个数字也可以写成基数10的分数。然而,我们写为10进制分数的任何东西都很难用二进制表示。在0.01、0.02、0.03…0.99的范围内,只有三个数字可以用我们的FP格式表示:0.25、0.50和0.75,因为它们是1/4、1/2和3/4,所有的数字都只使用2n项。
在base10中,我们不能表示1/3。但在二进制中,我们不能做1/10或1/3。
因此,虽然每一个二进制分数都可以用十进制来表示,但反过来却不正确。事实上,大多数小数在二进制中重复。
处理它
开发人员通常被要求进行<epsilon比较,更好的建议可能是舍入为整数值(在C库中:round()和round f(),即保持FP格式),然后进行比较。舍入到特定的小数部分长度可以解决大多数输出问题。
此外,在实数运算问题(FP是在早期昂贵的计算机上为之发明的问题)上,宇宙的物理常数和所有其他测量值只为相对较少的有效数字所知,因此整个问题空间无论如何都是“不精确的”。FP“精度”在这种应用中不是问题。
当人们尝试使用FP进行计数时,整个问题就真的出现了。它确实可以做到这一点,但前提是你坚持使用整数值,这会破坏使用它的意义。这就是为什么我们拥有所有这些小数软件库的原因。
我喜欢克里斯的披萨回答,因为它描述了实际问题,而不仅仅是关于“不准确”的通常手写。如果FP只是“不准确”,我们可以修复它,而且几十年前就已经做到了。我们没有这样做的原因是因为FP格式紧凑快速,是处理大量数字的最佳方式。此外,这也是太空时代和军备竞赛以及早期使用小型内存系统解决速度非常慢的计算机的大问题的尝试所留下的遗产。(有时,单个磁芯用于1位存储,但这是另一回事。)
结论
如果您只是在银行数豆子,那么首先使用十进制字符串表示的软件解决方案工作得非常好。但你不能这样做量子色动力学或空气动力学。
我可以补充一下吗;人们总是认为这是一个计算机问题,但如果你用手(以10为基数)计算,你就不能得到(1/3+1/3=2/3)=真,除非你有无穷大可以将0.333…加到0.333……就像(1/10+2/10)一样==基数2的3/10问题,您将其截断为0.333+0.333=0.666,并可能将其舍入为0.667,这在技术上也是不准确的。
用三进制数,三分之三不是问题——也许有人会问为什么你的十进制数学被打破了。。。
一些统计数据与这个著名的双精度问题有关。
当使用0.1(从0.1到100)的步长将所有值(a+b)相加时,精度误差的概率约为15%。请注意,该错误可能会导致稍大或稍小的值。以下是一些示例:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
当使用0.1(从100到0.1)的步长减去所有值(a-b,其中a>b)时,我们有大约34%的精度误差。以下是一些示例:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
*15%和34%确实是巨大的,所以当精度非常重要时,请始终使用BigDecimal。使用2个十进制数字(步骤0.01),情况会进一步恶化(18%和36%)。