考虑以下代码:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些错误?


当前回答

我的解决方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度是指在加法过程中要保留小数点后的位数。

其他回答

这些奇怪的数字之所以出现,是因为计算机使用二进制(以2为基数)数字系统进行计算,而我们使用十进制(以10为基数)。

大多数分数不能用二进制或十进制或两者精确表示。结果-四舍五入(但精确)的数字结果。

浮点舍入错误。由于缺少5的素因子,0.1在基-2中不能像在基-10中那样精确地表示。正如1/3以十进制表示需要无限位数,但以3为基数表示为“0.1”,0.1以2为基数表示,而以10为基数不表示。计算机没有无限的内存。

十进制数(如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上不支持它们)。

我的解决方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

精度是指在加法过程中要保留小数点后的位数。

除了其他正确答案之外,您可能还需要考虑缩放值以避免浮点运算的问题。

例如:

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页)。