避免JavaScript奇怪的十进制计算问题

时间:2011-02-18 05:06:40

标签: javascript math decimal

I just read on MDN,由于所有“双精度64位格式IEEE 754值”,JS处理数字的一个怪癖是当你做{ {1}}你得到.2 + .1(这就是文章的内容,但我在Firefox中得到0.30000000000000004)。因此:

0.29999999999999993

评估为(.2 + .1) * 10 == 3

这似乎很成问题。那么可以采取哪些措施来避免因JS中不精确的十进制计算而导致的错误?

我注意到,如果你做false,你会得到正确答案。那么你应该避免任何涉及小于1的数值的数学吗?因为这似乎非常不切实际。在JS中进行数学运算还有其他危险吗?

修改
我知道许多小数部分不能存储为二进制,但我遇到的大多数其他语言似乎处理错误(如JS处理大于1的数字)似乎更直观,所以我不习惯这就是为什么我想看看其他程序员如何处理这些计算的原因。

6 个答案:

答案 0 :(得分:28)

1.2 + 1.1可能没问题,但0.2 + 0.1可能不行。

这几乎是当今使用的每种语言中的问题。问题是1/10不能准确地表示为二进制分数,就像1/3不能用小数表示一样。

变通办法包括四舍五入到你需要的小数位数,并使用字符串,这是准确的:

(0.2 + 0.1).toFixed(4) === 0.3.toFixed(4) // true

或者您可以在此之后将其转换为数字:

+(0.2 + 0.1).toFixed(4) === 0.3 // true

或使用Math.round:

Math.round(0.2 * X + 0.1 * X) / X === 0.3 // true

其中X是10的幂,例如100或10000 - 取决于您需要的精度。

或者在数钱时你可以用美分代替美元:

cents = 1499; // $14.99

这样你只使用整数,你根本不必担心十进制和二进制分数。

2017年更新

在JavaScript中表示数字的情况可能比以前复杂一些。 曾经是的情况我们在JavaScript中只有一种数字类型:

现在不再是这种情况 - 不仅今天JavaScript中有更多数字类型,还有更多数字类型,包括向ECMAScript添加任意精度整数的提议,希望如此,将遵循任意精度小数 - 有关详细信息,请参阅此答案:

另见

关于如何处理计算的一些例子的另一个相关答案:

答案 1 :(得分:21)

在这些情况下,你会倾向于使用epsilon估计。

像(伪代码)

之类的东西
if (abs(((.2 + .1) * 10) - 3) > epsilon)

其中epsilon类似于0.00000001,或者您需要的任何精度。

快速阅读Comparing floating point numbers

答案 2 :(得分:9)

(Math.floor(( 0.1+0.2 )*1000))/1000

这会降低浮点数的精度,但如果您不使用非常小的值,则可以解决问题。 例如:

.1+.2 =
0.30000000000000004

在建议的操作之后,你将得到0.3但是之间的任何值:

0.30000000000000000
0.30000000000000999

也将被视为0.3

答案 3 :(得分:5)

了解浮点运算中的舍入误差不适合胆小的人!基本上,计算就好像有无限的精度可用。然后根据相关IEEE规范中规定的规则对结果进行舍入。

这种舍入可以提出一些时髦的答案:

Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true

整个数量级 out。这是一些舍入错误!

对于任何浮点架构,都有一个数字代表可区分数字之间的最小间隔。它被称为EPSILON。

它将在不久的将来成为EcmaScript标准的一部分。在此期间,您可以按如下方式计算:

function epsilon() {
    if ("EPSILON" in Number) {
        return Number.EPSILON;
    }
    var eps = 1.0; 
    // Halve epsilon until we can no longer distinguish
    // 1 + (eps / 2) from 1
    do {
        eps /= 2.0;
    }
    while (1.0 + (eps / 2.0) != 1.0);
    return eps;
}

然后您可以使用它,如下所示:

function numericallyEquivalent(n, m) {
    var delta = Math.abs(n - m);
    return (delta < epsilon());
}

或者,由于舍入错误可能会惊人地累积,因此您可能希望使用delta / 2delta * delta而不是delta

答案 4 :(得分:3)

您需要一些错误控制。

做一点双重比较方法:

int CompareDouble(Double a,Double b) {
    Double eplsilon = 0.00000001; //maximum error allowed

    if ((a < b + epsilon) && (a > b - epsilon)) {
        return 0;
    }
    else if (a < b + epsilon)
        return -1;
    }
    else return 1;
}

答案 5 :(得分:3)

libraries that seek to solve this problem但如果您不想包含其中一个(或因某些原因不能,比如在GTM variable内工作),那么您可以使用我写的这个小函数:

用法:

var a = 194.1193;
var b = 159;
a - b; // returns 35.11930000000001
doDecimalSafeMath(a, '-', b); // returns 35.1193

这是功能:

function doDecimalSafeMath(a, operation, b, precision) {
    function decimalLength(numStr) {
        var pieces = numStr.toString().split(".");
        if(!pieces[1]) return 0;
        return pieces[1].length;
    }

    // Figure out what we need to multiply by to make everything a whole number
    precision = precision || Math.pow(10, Math.max(decimalLength(a), decimalLength(b)));

    a = a*precision;
    b = b*precision;

    // Figure out which operation to perform.
    var operator;
    switch(operation.toLowerCase()) {
        case '-':
            operator = function(a,b) { return a - b; }
        break;
        case '+':
            operator = function(a,b) { return a + b; }
        break;
        case '*':
        case 'x':
            precision = precision*precision;
            operator = function(a,b) { return a * b; }
        break;
        case '÷':
        case '/':
            precision = 1;
            operator = function(a,b) { return a / b; }
        break;

        // Let us pass in a function to perform other operations.
        default:
            operator = operation;
    }

    var result = operator(a,b);

    // Remove our multiplier to put the decimal back.
    return result/precision;
}