避免JavaScript出现问题';这是奇怪的十进制计算

避免JavaScript出现问题';这是奇怪的十进制计算,javascript,math,decimal,Javascript,Math,Decimal,我发现JS处理数字的一个怪癖是,由于所有东西都是“双精度64位格式IEEE 754值”,当你做一些类似.2+.1的事情时,你会得到0.300000000000000004(这是文章读到的,但我在Firefox中得到0.299999999999993)。因此: (.2 + .1) * 10 == 3 计算结果为false 这似乎是一个很大的问题。那么,可以做些什么来避免由于JS中不精确的十进制计算而导致的错误呢 我注意到如果你做了1.2+1.1你会得到正确的答案。那么,你应该避免使用任何一种数值

我发现JS处理数字的一个怪癖是,由于所有东西都是“双精度64位格式IEEE 754值”,当你做一些类似
.2+.1
的事情时,你会得到
0.300000000000000004
(这是文章读到的,但我在Firefox中得到
0.299999999999993
)。因此:

(.2 + .1) * 10 == 3
计算结果为
false

这似乎是一个很大的问题。那么,可以做些什么来避免由于JS中不精确的十进制计算而导致的错误呢

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

编辑:

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

在这种情况下,你更愿意使用ε估计

类似于(伪代码)

式中,ε大约为0.00000001,或任何您需要的精度


快速阅读

您需要一点错误控制

做一个小的双重比较法:

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;
}
int CompareDouble(双a,双b){
Double eplsilon=0.00000001;//允许的最大错误数
if((ab-ε)){
返回0;
}
否则如果(a
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中只有一种数字类型:

  • 64位浮点(参见:和)
情况已不再如此-如今,JavaScript中不仅有更多的数字类型,而且还有更多的数字类型,包括向ECMAScript中添加任意精度整数的建议,希望任意精度小数也会随之出现-有关详细信息,请参阅以下答案:

另见 另一个相关答案,并举例说明如何处理计算:

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

.1+.2 =
0.30000000000000004
在建议的操作后,您将得到0.3,但任何值介于:

0.30000000000000000
0.30000000000000999

也将被认为是0.3

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

这个四舍五入可以给出一些古怪的答案:

Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true
这是一个完整的数量级。这是一个舍入误差

对于任何浮点体系结构,都有一个数字表示可分辨数字之间的最小间隔。它被称为ε

在不久的将来,它将成为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());
}
函数数值等效(n,m){
var delta=数学绝对值(n-m);
返回(delta
或者,由于舍入误差可能会以惊人的速度累积,您可能希望使用
delta/2
delta*delta
而不是
delta
,但如果您不想包含其中一个(或者由于某些原因不能,例如在a中工作),那么您可以使用我编写的这个小函数:

用法:

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

在任何语言中,浮点数几乎无法与其他数字完美比较。舍入是你的朋友。@mellamokb:那么你必须将所有数乘以10的幂,然后舍入到最接近的整数?@deceze:PHP,MySQL,Perl,VB,我过去使用过的大多数语言在这些类型的计算中从来没有遇到过任何问题。@Lèse
$php-r'var_dump((0.1+0.2)*10==3);'
bool(false)
:)我想你把
)放错地方了,否则就完全同意了。仅供将来的访问者参考,链接的文章现在在there's now Number.EPSILON在Chrome和Firefox中-MDN参考:x=0.2;y=0.3;z=0.1;equal=(Math.abs(x-y+z)toFixed
返回数字的字符串表示形式,不确定这是否是您想要的。很好的解释!谢谢你的ε函数。我找到了一个有用的相等性测试,并将其放在这里:。
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;
}