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

IT技术 javascript math decimal
2021-02-20 16:48:17

刚刚在 MDN读到,由于一切都是“双精度 64 位格式 IEEE 754 值”,JS 处理数字的怪癖之一是当你做类似.2 + .1你得到的事情时0.30000000000000004(这就是文章读的内容,但我明白了0.29999999999999993在 Firefox 中)。所以:

(.2 + .1) * 10 == 3

评估为false.

这看起来会很成问题。那么如何避免由于 JS 中的十进制计算不精确而导致的错误呢?

我注意到如果你这样做了,1.2 + 1.1你就会得到正确的答案。那么你应该避免任何涉及小于 1 的值的数学吗?因为这看起来很不切实际。在 JS 中做数学还有其他危险吗?

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

6个回答

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 添加任意精度整数的提议,并且希望任意精度的小数会随之而来 - 请参阅此答案详情:

也可以看看

另一个相关答案,其中包含一些有关如何处理计算的示例:

很好的解释!
2021-04-21 16:48:17
toFixed 返回一个数字的字符串表示,不确定这是否是你想要的。
2021-05-04 16:48:17

在这种情况下,您通常宁愿使用 epsilon 估计。

类似(伪代码)

if (abs(((.2 + .1) * 10) - 3) > epsilon)

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

快速阅读比较浮点数

我认为你在)那里放错了地方,否则完全同意。
2021-04-18 16:48:17
现在 Chrome 和 Firefox 中有 Number.EPSILON - MDN 参考:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... x = 0.2; y = 0.3; z = 0.1;等于 = (Math.abs(x - y + z) < Number.EPSILON);
2021-04-19 16:48:17
仅供未来访问者参考,链接文章现在位于randomascii.wordpress.com/category/floating-point
2021-05-03 16:48:17
(Math.floor(( 0.1+0.2 )*1000))/1000

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

.1+.2 =
0.30000000000000004

建议的操作后,您将获得 0.3 但介于以下之间的任何值:

0.30000000000000000
0.30000000000000999

也将被视为 0.3

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

用法:

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;
}

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

这种四舍五入可能会抛出一些时髦的答案:

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

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

对于任何浮点架构,都有一个数字代表可区分数字之间的最小间隔。它被称为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

感谢您提供出色的 epsilon 功能。我找到了一个有用的相等性测试并将其放在这里:stackoverflow.com/a/20957752/1691517
2021-04-24 16:48:17