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

Lès*_*sté 32 javascript math decimal

刚刚在MDN上看到JS由于所有"双精度64位格式IEEE 754值"而处理数字的怪癖之一就是当你做的事情就像.2 + .1你得到的那样0.30000000000000004(这就是文章的内容,但我得到了0.29999999999999993在Firefox).因此:

(.2 + .1) * 10 == 3
Run Code Online (Sandbox Code Playgroud)

评估为false.

这似乎是非常有问题的.那么可以做些什么来避免由于JS中不精确的十进制计算而导致的错误?

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

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

rsp*_*rsp 32

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

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

解决方法包括舍入到您需要的小数位数,并使用字符串,这是准确的:

(0.2 + 0.1).toFixed(4) === 0.3.toFixed(4) // true
Run Code Online (Sandbox Code Playgroud)

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

+(0.2 + 0.1).toFixed(4) === 0.3 // true
Run Code Online (Sandbox Code Playgroud)

或使用Math.round:

Math.round(0.2 * X + 0.1 * X) / X === 0.3 // true
Run Code Online (Sandbox Code Playgroud)

其中X一些功率为10,例如100或10000 - 取决于您需要的精度.

或者你可以在计算金钱时使用美分而不是美元:

cents = 1499; // $14.99
Run Code Online (Sandbox Code Playgroud)

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

2017年更新

在JavaScript中表示数字的情况可能比以前复杂一些.它曾经是的情况下,我们在JavaScript中只有一个数值类型:

现在不再是这样 - 不仅今天JavaScript中有更多的数字类型,还有更多的数字类型,包括向ECMAScript添加任意精度整数的建议,并且希望随后的任意精度小数 - 请参阅此答案详情:

也可以看看

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

  • '真实'答案 (2认同)
  • `toFixed`返回一个数字的字符串表示,不确定这是否是你想要的. (2认同)

Adr*_*der 24

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

像(伪代码)的东西

if (abs(((.2 + .1) * 10) - 3) > epsilon)
Run Code Online (Sandbox Code Playgroud)

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

快速阅读比较浮点数

  • 现在Chrome和Firefox中的Number.EPSILON - MDN参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON x = 0.2; y = 0.3; z = 0.1; equal =(Math.abs(x - y + z)<Number.EPSILON); (3认同)

小智 9

(Math.floor(( 0.1+0.2 )*1000))/1000
Run Code Online (Sandbox Code Playgroud)

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

.1+.2 =
0.30000000000000004
Run Code Online (Sandbox Code Playgroud)

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

0.30000000000000000
0.30000000000000999
Run Code Online (Sandbox Code Playgroud)

也将被视为0.3


Eri*_*and 6

有一些库试图解决这个问题,但如果你不想包含其中之一(或者由于某种原因不能包含,比如在GTM 变量中工作),那么你可以使用我写的这个小函数:

用法:

var a = 194.1193;
var b = 159;
a - b; // returns 35.11930000000001
doDecimalSafeMath(a, '-', b); // returns 35.1193
Run Code Online (Sandbox Code Playgroud)

这是函数:

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;
}
Run Code Online (Sandbox Code Playgroud)


Yoc*_*mer 5

您需要一些错误控制。

做一个小小的双重比较方法:

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;
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*ams 5

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

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

Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true
Run Code Online (Sandbox Code Playgroud)

这是一个完整的数量级.这是一些舍入错误!

对于任何浮点架构,都有一个数字代表可区分数字之间的最小间隔.它被称为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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

或者,由于舍入误差可累积得惊人,你可能要使用delta / 2delta * delta不是delta.