web-dev-qa-db-ja.com

JavaScriptで浮動小数点数の精度をどう扱うか?

次のようなダミーのテストスクリプトがあります。

function test(){
    var x = 0.1 * 0.2;
    document.write(x);
}
test();

これは結果0.020000000000000004を出力しますが、0.02を出力するはずです(電卓を使用している場合)。私が理解した限りでは、これは浮動小数点の乗算精度の誤差によるものです。

そのような場合に正しい結果0.02が得られるように誰かが良い解決策を持っていますか?私はtoFixedのような関数があることを知っていますまたは丸めは別の可能性があるでしょうが、私は本当に丸めたり丸めたりせずに整数を印刷してもらいたいのです。あなたのうちの1人が素敵でエレガントな解決策を持っているかどうかを知りたいだけでした。

もちろん、それ以外の場合は10桁程度に丸めます。

498
Juri

浮動小数点ガイドから

この問題を回避するにはどうすればよいですか。

それはあなたがどんな種類の計算をしているかによって異なります。

  • 特にお金を使って作業するときに、結果を正確に合計する必要がある場合は、特別なdecimalデータ型を使用してください。
  • 余分な小数点以下の桁数をすべて表示したくない場合は、表示時に結果を固定小数点以下の桁数に丸めてフォーマットします。
  • 利用可能なdecimalデータ型がない場合は、代替方法として整数を使用します。お金の計算はすべてセントで行います。しかし、これはもっと手間がかかり、いくつかの欠点があります。

最初のポイントは、特定の正確なdecimal動作が本当に必要な場合にのみ適用されることに注意してください。ほとんどの人はそれを必要としません、彼らは彼らのプログラムが1/3のように起こったなら同じエラーでさえ点滅しないだろうということに気づかずに彼らのプログラムが1/10のような数で正しく働かないことにただイライラしています。

最初のポイントが本当にあなたに当てはまるのであれば、 BigDecimal for JavaScript を使用してください。これはまったくエレガントではありませんが、不完全な回避策を提供するのではなく実際に問題を解決します。

416

私はPedro Ladariaのソリューションが好きで、似たようなものを使っています。

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

Pedrosのソリューションとは異なり、これは0.999 ...の切り上げになり、最下位桁のプラス/マイナス1に正確になります。

注:32または64ビット浮動小数点数を扱うときは、最良の結果を得るためにtoPrecision(7)およびtoPrecision(15)を使用する必要があります。理由については この質問 を参照してください。

106
linux_mike

数学的に傾いている人のために: http://docs.Oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

推奨される方法は、補正係数を使用することです(算術演算が整数間で行われるように適切な10の乗数を掛けます)。たとえば、0.1 * 0.2の場合、補正係数は10であり、計算を実行しています。

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

(非常に迅速な)解決策は次のようになります。

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

この場合:

> Math.m(0.1, 0.2)
0.02

SinfulJS のようなテスト済みのライブラリを使うことを強く勧めます。

62
SheetJS

あなたは掛け算を実行しているだけですか?もしそうなら、あなたはあなたの有利に10進数算術に関するきちんとした秘密を使用することができます。それがNumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimalsです。つまり、0.123 * 0.12がある場合、0.123には小数点以下3桁があり、0.12には2桁あるので、小数点以下5桁があることがわかります。したがって、JavaScriptが0.014760000002のような数値を与えた場合、精度を失うことを恐れずに安全に小数点以下第5位に丸めることができます。

39
Nate Zaugg

JavaScript用のsprintf実装を探しているので、小さなエラーを含むfloatを(バイナリフォーマットで格納されているため)期待通りのフォーマットで書き出すことができます。

javascript-sprintf を試してください、あなたはこれを次のように呼ぶでしょう:

var yourString = sprintf("%.2f", yourNumber);

小数点以下2桁のfloatとしてあなたの番号をプリントアウトする。

表示精度を上げるために Number.toFixed() を使用することもできます。これは、指定された精度に浮動小数点を丸めるためだけにファイルを追加したくない場合です。

24
Douglas

BigNumber.js は私のニーズを満たしています。

任意精度の10進数および非10進数演算用のJavaScriptライブラリ。

それは良い ドキュメント を持っていてそして作者はフィードバックに応えて非常に勤勉です。

同じ作者には他に2つの似たライブラリがあります。

Big.js

任意精度の10進数算術演算用の小型で高速なJavaScriptライブラリー。 bignumber.jsの妹。

and Decimal.js

JavaScript用の任意精度のDecimal型。

これがBigNumberを使ったコードです。

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>
16
Ronnie Overby

この関数は、2つの浮動小数点数の乗算から必要な精度を決定し、適切な精度で結果を返します。そうではありませんがエレガント。

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}
14
Gabriel
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---または---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---また---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

---のように---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2
14
shawndumas

あなたは実際に何桁の小数桁数を実際に欲しいかについて決心しなければなりません - ケーキを持ってそれを食べることもできません:-)

それ以降のすべての操作で数値エラーが累積されます。早めにカットしないと、単に大きくなります。きれいに見える結果を提示する数値ライブラリは単に各ステップで最後の2桁を切り捨てます、数値コプロセッサも同じ理由で「通常」と「完全」の長さを持っています。 Cuf-offはプロセッサにとっては安価ですが、スクリプトでは非常に高価です(乗算と除算、およびpov(...)の使用)。良い数学libはあなたのためにカットオフをするためにfloor(x、n)を提供するでしょう。

それで、少なくとも、あなたはpov(10、n)で大域的なvar/constantを作るべきです - それはあなたがあなたが必要とする精度を決めたことを意味します:-)それから:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

表示しているだけで結果をif-sしていないと仮定すると、数学を続けて最後までカットオフすることもできます。それができれば、.toFixed(...)の方が効率的です。

もしあなたがif-s /比較をしていてカットしたくないのなら、通常epsと呼ばれる小さな定数も必要です。あなたのカットオフが最後の2桁であるとしましょう - それであなたのepsは最後から3番目に1を持っています(3番目に重要でない)そしてあなたは結果が期待されるepsの範囲内にあるかどうか比較するためにそれを使うことができます* 0.2 <0.02 + eps)。

11
ZXX

Phpjs.orgのround()関数はうまく機能します: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
8
Tom

驚くべきことに、この機能はまだ投稿されていませんが、他にも同様のバリエーションがあります。これは、Math.round()のMDN Webドキュメントからのものです。それは簡潔で、そしてさまざまな精度を可能にします。

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log(precisionRound(1234.5678、1)); //予想される出力:1234.6

console.log(precisionRound(1234.5678、-1)); //予想される出力:1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>
8
HelloWorldPeace

得られた結果は、異なる言語、プロセッサ、およびオペレーティングシステムにおける浮動小数点の実装間で正確かつかなり一貫しています。変化が生じる唯一のことは、floatが実際には2倍(またはそれ以上)の場合の不正確さのレベルです。

2進数の浮動小数点数の0.1は10進数の1/3のようなものです(つまり、0.3333333333333 ...永遠に)、正確に扱う方法はありません。

浮動小数点数を扱う場合は、 always 小さな丸め誤差が予想されるので、表示された結果を賢明なものに丸める必要があります。その代わりに、すべての計算はプロセッサのネイティブバイナリで行われるため、非常に高速で強力な算術演算が得られます。

ほとんどの場合、解決策は固定小数点演算に切り替えることではありません。これは主に、非常に低速で99%の精度しか必要ないためです。そのレベルの正確さを必要とするもの(金融取引など)を扱っている場合は、Javascriptはおそらく最良のツールではありません(固定小数点型を強制したい場合は、静的言語のほうがおそらく適切です)。 ).

あなたはエレガントな解決策を探しています、それが私はこれが怖いです:フロートは速いですが小さな丸め誤差があります - それらの結果を表示するときはいつも賢明なものに丸めます。

6
Keith

これを避けるためには、浮動小数点の代わりに整数値を使うべきです。そのため、2桁の精度で* 100の値を使用する場合は、3桁の場合は1000を使用します。表示する場合は、フォーマッターを使用して区切り記号を入れます。

多くのシステムでは、このように小数を扱うことを省きます。これが、多くのシステムがドル/ユーロ(浮動小数点)ではなくセント(整数)で動作する理由です。

6
Gertjan

小規模な操作でこの問題を回避したい場合は、parseFloat()toFixed()を使用できます。

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
5
Softwareddy

0.6 * 3それは素晴らしいです!))私にとってこれはうまくいきます:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

非常に非常にシンプル))

問題

浮動小数点は、すべての小数値を正確に格納することはできません。そのため、浮動小数点フォーマットを使用すると、入力値には常に丸め誤差があります。入力上のエラーは、もちろん出力上のエラーにもなります。離散関数または演算子の場合、関数または演算子が離散している点を中心に、出力に大きな違いがあります。

浮動小数点値の入出力

したがって、浮動小数点変数を使用するときは、常にこれに注意する必要があります。そして、あなたが浮動小数点での計算からあなたが望むどんな出力でもこれを念頭に置いてこれを表示する前に常にフォーマットされる/条件付けされるべきです。
連続する関数と演算子だけが使われるとき、望ましい精度への丸めはしばしばするでしょう(切り捨てないでください)。浮動小数点数を文字列に変換するために使用される標準のフォーマット機能は、通常これを行います。
四捨五入は合計誤差を所望の精度の半分以上にすることがある誤差を加えるので、出力は入力の期待精度および所望の出力精度に基づいて補正されるべきである。あなたがすべき

  • 予想される精度に入力を丸めるか、またはより高い精度で値を入力できないようにします。
  • 四捨五入/フォーマットを行う前に、希望する精度の1/4以下で、入力時および計算時の丸め誤差によって生じる最大予想誤差よりも大きい小さい値を出力に追加します。それが不可能な場合は、使用されているデータ型の精度の組み合わせでは、計算に必要な出力精度を実現するのに十分ではありません。

これら2つのことは通常行われておらず、ほとんどの場合それらを行わないことによって生じる違いはほとんどのユーザーにとって重要であるには小さすぎますが、私は出力がそれらの修正なしでユーザーに受け入れられないプロジェクトを既に持っています。

離散関数または演算子(モジュラのように)

個別の演算子または関数が関係している場合は、出力が期待どおりになるようにするために追加の修正が必要になることがあります。四捨五入してから四捨五入する前に小さな修正を加えても問題は解決しません。
離散関数または演算子を適用した直後に、中間計算結果の特別なチェック/修正が必要になる場合があります。特定の場合(モジュラ演算子)については、私の質問に対する回答を参照してください。 モジュラス演算子がJavaScriptで小数値を返すのはなぜですか?

問題を抱えないようにしてください

このような計算にデータ型(整数または固定小数点形式)を使用すると、丸め誤差なしに予想される入力を格納できるため、これらの問題を回避するほうがより効率的です。その一例は、財務計算に浮動小数点値を使用してはいけないということです。

5

一般的な用途では、この動作は許容される可能性が高いことに注意してください。
これらの浮動小数点値を比較して適切なアクションを決定すると、問題が発生します。
ES6の出現により、許容可能なエラーマージンを決定するために新しい定数Number.EPSILONが定義されました。
したがって、このような比較を実行する代わりに

0.1 + 0.2 === 0.3 // which returns false

このようにカスタムの比較関数を定義することができます。

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

ソース: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon

3
user10089632

ここ を見ることができる私のchiliadic算術ライブラリを試してみてください。それ以降のバージョンが必要な場合は、入手できます。

3
Robert L

enter image description here

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
3
Ashish Singhal

ほとんどの小数は、2進浮動小数点型(ECMAScriptが浮動小数点値を表すのに使用しているもの)で正確に表現することはできません。そのため、任意精度の算術型または10進数ベースの浮動小数点型を使用しない限り、優雅な解決策はありません。例えば、 Windowsに同梱されている電卓アプリは現在、この問題を解決するために任意精度の算術演算を使用しています

3
MSN

固定小数点演算 を見てください。操作したい数値の範囲が狭い場合(通貨など)、おそらく問題は解決します。私はそれを少数の10進数に四捨五入するでしょう、そしてそれは最も簡単な解決策です。

3
Marius

私はmod 3で厄介な丸め誤差の問題を抱えていました。時々私は0を得なければならないとき私は.000 ... 01を得ます。これは、取り扱いが簡単で、<= .01でテストするだけです。しかし、時々私は2.99999999999998を得るでしょう。痛い!

BigNumbers 問題を解決したが、もう少し皮肉な問題を紹介した。 BigNumbersに8.5をロードしようとしたとき、それが本当に8.4999であることを知らされました…そして15桁以上の有効数字を持っていました。これはBigNumbersがそれを受け入れることができなかったことを意味しました(私はこの問題はやや皮肉なことだと私は言ったと思います)。

皮肉問題の簡単な解決策:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);
2
mcgeo52

つかいます

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
1
Himadri

decimal.jsbig.js または bignumber.js を使用して、Javascriptでの浮動小数点操作の問題を回避できます。

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js:ミニマリスト。使いやすい;小数点以下の桁数で指定された精度。除算にのみ適用される精度。

bignumber.js:基数2-64;構成オプション。 NaN;無限大;小数点以下の桁数で指定された精度。除算のみに適用される精度。ベースプレフィックス。

decimal.js:基数2-64;構成オプション。 NaN;無限大;非整数の累乗、exp、ln、log;有効数字で指定された精度。常に適用される精度。乱数。

詳細な比較へのリンク

1

その通りです、その理由は浮動小数点数の限られた精度です。有理数を2つの整数の除算として格納すると、ほとんどの場合、精度を損なうことなく数を格納できます。印刷に関しては、結果を端数として表示することができます。私が提案した表現では、それは簡単になります。

もちろん、これは非合理的な数字ではあまり役に立ちません。しかし、問題が最小になるように計算を最適化したい場合があります(例:sqrt(3)^2)のような状況の検出)。

1
skalee

エレガントではありませんが仕事はします(末尾のゼロを削除します)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
1
Peter

Number(1.234443).toFixed(2)を使用してください。それは1.23を印刷します

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();
1
Harish.bazee

以下の機能を使用して出力します。

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

出力toFixedCurrency(x)に注意してください。

0
Júlio Paulillo

私はあまりプログラミングが得意ではありませんが、このトピックに本当に興味があったので、ライブラリやスクリプトを使用せずにそれを解決する方法を理解しようとしました

私はこれをスクラッチパッドに書いた

var toAlgebraic = function(f1, f2) {
    let f1_base = Math.pow(10, f1.split('.')[1].length);
    let f2_base = Math.pow(10, f2.split('.')[1].length);
    f1 = parseInt(f1.replace('.', ''));
    f2 = parseInt(f2.replace('.', ''));

    let dif, base;
    if (f1_base > f2_base) {
        dif = f1_base / f2_base;
        base = f1_base;
        f2 = f2 * dif;
    } else {
        dif = f2_base / f1_base;
        base = f2_base;
        f1 = f1 * dif;
    }

    return (f1 * f2) / base;
};

console.log(0.1 * 0.2);
console.log(toAlgebraic("0.1", "0.2"));

私はプログラミングが得意ではないので、あなたはこのコードをリファクタリングする必要があるかもしれません:)

0
Jeeva

2つのfloat値を足し合わせても正確な値が得られないので、これを比較するのに役立つ特定の数に固定する必要があります。

0
aditya

これは私のために働く:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54
0
Antonio Max

毎回関数を呼び出さなければならないことを考えたくない場合は、変換を処理するClassを作成できます。

class Decimal {
  constructor(value = 0, scale = 4) {
    this.intervalValue = value;
    this.scale = scale;
  }

  get value() {
    return this.intervalValue;
  }

  set value(value) {
    this.intervalValue = Decimal.toDecimal(value, this.scale);
  }

  static toDecimal(val, scale) {
    const factor = 10 ** scale;
    return Math.round(val * factor) / factor;
  }
}

使用法:

const d = new Decimal(0, 4);
d.value = 0.1 + 0.2;              // 0.3
d.value = 0.3 - 0.2;              // 0.1
d.value = 0.1 + 0.2 - 0.3;        // 0
d.value = 5.551115123125783e-17;  // 0
d.value = 1 / 9;                  // 0.1111

もちろん、Decimalを扱うときには注意が必要です。

d.value = 1/3 + 1/3 + 1/3;   // 1
d.value -= 1/3;              // 0.6667
d.value -= 1/3;              // 0.3334
d.value -= 1/3;              // 0.0001

あなたは理想的には(12のような)ハイスケールを使い、そしてそれをあなたがそれを提示するかそれをどこかに保存する必要がある時にそれを変換したいと思うでしょう。個人的には、UInt8Arrayを作成して精度値を作成しようとしました(SQLのDecimal型によく似ています)が、Javascriptでは演算子をオーバーロードできないため、基本的な数学演算子を使用できないのでちょっと面倒です。 (+-/*)および代わりにadd()substract()mult()のような関数を使用します。私にとっては、それだけの価値はありません。

しかし、あなたがそのレベルの精度を必要としていて、数学のために関数の使用に耐えて喜んでいるならば、私は decimal.js ライブラリを推薦します。

0
ShortFuse