web-dev-qa-db-ja.com

JavaScriptの奇妙な10進数計算の問題を回避する

I MDNで読むだけ すべてがであるためにJSが数値を処理する際の癖の1つは、「倍精度64ビット形式のIEEE 754値」です。 .2 + .1取得します0.30000000000000004(記事ではこれを読みますが、0.29999999999999993(Firefoxの場合)。したがって:

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

falseと評価されます。

これは非常に問題があるようです。それでは、JSの10進数の計算が不正確であることによるバグを回避するために何ができるでしょうか?

私はあなたがそうするなら1.2 + 1.1正しい答えが得られます。それで、1未満の値を含むあらゆる種類の数学を避けるべきですか?それは非常に非現実的だと思われるからです。 JSで数学をすることに他の危険はありますか?

編集:
私は多くの小数部をバイナリとして保存できないことを理解していますが、私が遭遇した他のほとんどの言語がエラーを処理するように見える方法(JSが1より大きい数値を処理するなど)はより直観的に見えるので、私はこれには慣れていないので、他のプログラマがこれらの計算をどのように処理するかを確認したいのです。

33
Lèse majesté

このような状況では、典型的にはイプシロン推定を利用します。

(疑似コード)のようなもの

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

ここで、イプシロンは0.00000001のようなもの、または必要な精度です。

浮動小数点数の比較 を一読してください

22
Adriaan Stander

1.2 + 1.1は問題ないかもしれませんが、0.2 + 0.1は問題があるかもしれません。

これは、今日使用されているほぼすべての言語の問題です。問題は、1/3が小数として表現できないのと同じように、1/10を2進数として正確に表現できないことです。

回避策には、必要な小数点以下の桁数への丸めと、正確な文字列の処理のいずれかが含まれます。

(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

そうすれば、整数だけを扱うことができ、10進数と2進数の分数をまったく気にする必要がありません。

2017年アップデート

JavaScriptで数値を表現する状況は、以前よりも少し複雑になる場合があります。 JavaScriptでは数値型が1つしかなかったことがかつてでした:

これはもう当てはまりません-現在、JavaScriptにはより多くの数値型があるだけでなく、任意を追加する提案を含む、より多くが進んでいます- ECMAScriptへの精度整数、そしてできれば任意精度の小数が続きます-詳細についてはこの回答を参照してください:

こちらもご覧ください

計算を処理する方法のいくつかの例を使用した別の関連する回答:

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

これにより、浮動小数点数の精度が低下しますが、非常に小さな値で作業していない場合は問題が解決します。例えば:

.1+.2 =
0.30000000000000004

提案された操作の後、0.3を取得しますが、次のいずれかの値になります。

0.30000000000000000
0.30000000000000999

0.3と見なされます

9

浮動小数点演算の丸め誤差を理解することは、気の弱い人には向いていません。基本的に、計算は無限の精度のビットが利用可能であるかのように行われます。結果は、関連する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ではなくdelta / 2またはdelta * deltaを使用することもできます。

5
Chris Williams

エラー制御が少し必要です。

少し二重の比較方法を作成します。

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;
}
3
Yochai Timmer

この問題を解決しようとする ライブラリがあります が、それらの1つを含めたくない場合(または作業などの何らかの理由で含めることができない場合) 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;
}
3
Eric Seastrand