考虑以下代码:
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
为什么会出现这些错误?
当前回答
十进制数(如0.1、0.2和0.3)在二进制编码浮点类型中没有精确表示。0.1和0.2的近似值之和与0.3的近似值不同,因此,0.1+0.2==0.3的错误在这里可以更清楚地看到:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
输出:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
为了更可靠地计算这些计算,您需要对浮点值使用基于十进制的表示。C标准没有默认指定此类类型,而是作为技术报告中描述的扩展。
_Decimal32、_Decimal64和_Decimal128类型可能在您的系统上可用(例如,GCC在选定的目标上支持它们,但Clang在OS X上不支持它们)。
其他回答
浮点舍入错误。由于缺少5的素因子,0.1在基-2中不能像在基-10中那样精确地表示。正如1/3以十进制表示需要无限位数,但以3为基数表示为“0.1”,0.1以2为基数表示,而以10为基数不表示。计算机没有无限的内存。
除了其他正确答案之外,您可能还需要考虑缩放值以避免浮点运算的问题。
例如:
var result = 1.0 + 2.0; // result === 3.0 returns true
…而不是:
var result = 0.1 + 0.2; // result === 0.3 returns false
在JavaScript中,表达式0.1+0.2===0.3返回false,但幸运的是,浮点中的整数运算是精确的,因此可以通过缩放来避免十进制表示错误。
作为一个实际的例子,为了避免精度至关重要的浮点问题,建议1将钱作为一个整数来处理:2550美分而不是25.50美元。
1 Douglas Crockford:JavaScript:好的部分:附录A——糟糕的部分(第105页)。
另一种方法是:使用64位来表示数字。因此,无法精确表示超过2**64=18446744073709551616个不同的数字。
然而,Math表示,在0和1之间已经有无限多的小数。IEE 754定义了一种编码,以有效地将这64位用于更大的数字空间加上NaN和+/-无穷大,因此在精确表示的数字之间存在间隙,只填充近似的数字。
不幸的是,0.3存在差距。
我可以补充一下吗;人们总是认为这是一个计算机问题,但如果你用手(以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,这在技术上也是不准确的。
用三进制数,三分之三不是问题——也许有人会问为什么你的十进制数学被打破了。。。
由于这篇文章对当前的浮点实现进行了一般性的讨论,我想补充一下,有一些项目正在解决它们的问题。
看看https://posithub.org/例如,它展示了一种称为posit(及其前身unum)的数字类型,它承诺以更少的比特提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。非常有趣的项目,背后的人是数学家约翰·古斯塔夫森博士。整个过程都是开源的,用C/C++、Python、Julia和C#实现了许多实际的实现(https://hastlayer.com/arithmetics).