JavaScript浮動小数点演算の精度問題

161050 ワード

問題の説明
JavaScriptには整数と浮動小数点が含まれています.  Number データの種類は、すべての数字は64ビットの浮動小数点として保存されています.整数も同じです.だから私たちはプリントしています.  1.00 このような浮動小数点の結果は  1 非を鳴らす  1.00 .いくつかの特殊な数値表現においては、例えば金額は、このように見られますが、少なくとも値は正しいです.しかし、命知らずなことに、浮動小数点は数学の演算をする時、よくいくつかの問題を発見して、いくつかの例を挙げます.
JavaScript :
  1. // =====================
  2. // 0.1 + 0.2 = 0.30000000000000004
  3. // 0.7 + 0.1 = 0.7999999999999999
  4. // 0.2 + 0.4 = 0.6000000000000001
  5. // 2.22 + 0.1 = 2.3200000000000003
  6.  
  7. // =====================
  8. // 1.5 - 1.2 = 0.30000000000000004
  9. // 0.3 - 0.2 = 0.09999999999999998
  10.  
  11. // =====================
  12. // 19.9 * 100 = 1989.9999999999998
  13. // 19.9 * 10 * 10 = 1990
  14. // 1306377.64 * 100 = 130637763.99999999
  15. // 1306377.64 * 10 * 10 = 130637763.99999999
  16. // 0.7 * 180 = 125.99999999999999
  17. // 9.7 * 100 = 969.9999999999999
  18. // 39.7 * 100 = 3970.0000000000005
  19.  
  20. // =====================
  21. // 0.3 / 0.1 = 2.9999999999999996
  22. // 0.69 / 10 = 0.06899999999999999
問題の原因
不思議なようです.小学生なら誰でもできる問題です.JavaScriptはできませんか?私たちはその本当の原因を見に来ました.
JavaScriptの数字は採用です. IEEE 754標準の64ビットダブル精度浮動小数点数.この規格は浮動小数点のフォーマットを定義しています.64ビットの浮動小数点のメモリ内の表示に対して、一番高い1ビットは符号ビットで、次に11ビットは指数で、残りの52ビットは有効数字です.
第0位:符号位、sは表して、0は正の数を表して、1は負の数を表します.第1位から第11位まで:指数部分を貯蓄して、eは表します.第12位から63位までは、小数部(すなわち有効数字)を格納し、fは、図のように:
記号のビットは一つの数の正負を決定し、指数の部分は数値の大きさを決定し、小数の部分は数値の精度を決定した.IEEE 754は、有効数字の第一位はデフォルトでは常に1であり、64ビットの浮動小数点の中には保存されないと規定している.つまり、有効数字はいつも1.xx…xxという形になります.その中でx.xxの部分は64ビットの浮動小数点の中に保存されています.一番長いのは52桁かもしれません.したがって、JavaScriptが提供する有効数字は、最長53のバイナリビット(64ビット浮動小数点の後52ビット+有効数字第1位の1)である.
計算プロセス
たとえばJavaScriptで計算します.  0.1 + 0.2時、いったい何があったのですか?
まず、十進数の0.10.2いずれも二進法に変換されますが、浮動小数点は二進法で表現する場合は無限です.例えば.
JavaScript :
  1. 0.1 -> 0.0001100110011001...( )
  2. 0.2 -> 0.0011001100110011...( )
IEEE 754規格の64ビットダブル精度浮動小数点の小数部は最大53ビットのバイナリビットをサポートしていますので、両者を加算した後、バイナリは以下の通りです.
JavaScript :
  1. 0.0100110011001100110011001100110011001100110011001100
浮動小数点数位の制限で切り捨てられた二進数を十進数に変換すればいいです.  0.30000000000000004.ですから、算術計算をする時に誤差が発生します.
整数の精度問題
Javascriptでは、整数精度にも問題があります.まず問題を見てみます.
JavaScript :
  1. console.log(19571992547450991); //=> 19571992547450990
  2. console.log(19571992547450991===19571992547450992); //=> true
同じ原因は、JavaScriptの中にある.  Numberタイプはまとめて浮動小数点で処理し、整数は最大54桁で最大(253 - 1Number.MAX_SAFE_INTEGER9007199254740991)と最小(-(253 - 1)Number.MIN_SAFE_INTEGER-9007199254740991)の安全整数範囲を計算します.この範囲を超えると、切り捨てられた精度の問題があります.
もちろんこの問題はJavascriptの中だけではないです.ほとんどのプログラミング言語はIEEE-755の浮動小数点表示法を採用しています.バイナリ浮動小数点を使ったプログラミング言語はいずれもこの問題があります.多くの他の言語の中には精度の問題を避けるためにパッケージされています.JavaScriptは弱いタイプの言語です.設計思想上は浮動小数点に対して厳密なデータタイプがないので、精度誤差の問題が際立っています.
ソリューション
上記では多くの問題と原因を説明しましたが、ここで解決策を提供します.
クラス
このような精度要求の高い計算は、バックエンドが成熟したライブラリを持っているので、バックエンドに計算と記憶を任せるべきです.先端にもいくつかのいいクラスがあります.
Math.js
Math.jsは専門的にJavaScriptとNode.jsのために提供する広範な数学の倉庫です.これは柔軟な表現解析器を持ち、シンボル計算をサポートし、多くの内蔵関数と定数を備え、異なるデータ型の像数、大きな数字(安全数を超える数字)、複数、分数、単位、行列を処理する統合ソリューションを提供します.機能が強く、使いやすいです.
公式サイト:http://mathjs.org/
GitHub:https://github.com/josdejong/mathjs
decimal.js
JavaScriptには10進数タイプの任意の精度値を提供します.
公式サイト:http://mikemcl.github.io/decimal.js/
GitHub:https://github.com/MikeMcl/decimal.js
ビッグ.js
公式サイト:http://mikemcl.github.io/big.js
GitHub:https://github.com/MikeMcl/big.js/
これらのクラスは多くの問題を解決してくれますが、通常は先端でこのような演算をします.表現層にしか使われません.応用はそんなに多くないです.したがって、多くの場合、一つの関数で解決できる問題は、クラスライブラリを参照して解決する必要はありません.
それぞれのより簡単な解決策を紹介します.
整数表示
整数については、Stringタイプの表現で値を取ったり、値を伝えたりすることができます.精度を失うことがあります.
数値、金額、小数などを書式設定します.
数値、金額を書式設定するだけなら、小数位をいくつか残しておくなど、ここを見ることができます. http://www.css88.com/archives/7324
浮動小数点演算
toFixed()方法
浮動小数点演算のソリューションはたくさんあります.ここでは現在よく使われているソリューションを提供します.浮動小数点演算結果を判断する前に計算結果を精度縮小します.
ToFixed()メソッドは定点表現法を使って一つの数を書式設定し、結果を四捨五入します.構文:
JavaScript :
  1. numObj.toFixed(digits)
パラメータ  digits 小数点以下の数を表します.0から20までの間で、環境がより広い範囲でサポートされる可能性があります.このパラメータを無視すると、デフォルトは0です.
数値の文字列表現形式を返します.指数表記法ではなく、小数点以下で  digits ビット数この値は必要に応じて四捨五入します.また、必要に応じて小数部を0で埋めて、小数部に指定された桁数があるようにします.値が大きい場合  1e+21この方法は簡単に呼び出すことができます.  Number.prototype.toString()指数記数法形式の文字列を返します.
特に注意してください.toFixed()は数値の文字列表現形式を返します.
具体的に見ることができます MDNにおける説明は、精度問題をこのように解決することができる.
JavaScript :
  1. parseFloat(( ).toFixed(digits)); // toFixed() 0 20
  2. //
  3. parseFloat((1.0 - 0.9).toFixed(10)) // 0.1
  4. parseFloat((0.3 / 0.1).toFixed(10)) // 3
  5. parseFloat((9.7 * 100).toFixed(10)) // 970
  6. parseFloat((2.22 + 0.1).toFixed(10)) // 2.32
古いバージョンのIEブラウザ(IE 6,7,8)では、toFixed()方法戻り値が必ずしも正確ではない.ですから、この方法は以前はあまり使われませんでした.ネットで検索した結果は大体次のような方法です.
他にもいくつかの解決策があります.簡単に浮動小数点を文字列に変換し、整数部分と小数点以下の部分を分離して整数に変換し、計算結果を計算したら浮動小数点に変換します.この過程はちょっと複雑です.インターネットで探してみます.
足し算関数
JavaScript :
  1. /**
  2. ** ,
  3. ** :javascript , 。 。
  4. ** :accAdd(arg1,arg2)
  5. ** :arg1 arg2
  6. **/
  7. function accAdd(arg1, arg2) {
  8. var r1, r2, m, c;
  9. try {
  10. r1 = arg1.toString().split(".")[1].length;
  11. }
  12. catch (e) {
  13. r1 = 0;
  14. }
  15. try {
  16. r2 = arg2.toString().split(".")[1].length;
  17. }
  18. catch (e) {
  19. r2 = 0;
  20. }
  21. c = Math.abs(r1 - r2);
  22. m = Math.pow(10, Math.max(r1, r2));
  23. if (c > 0) {
  24. var cm = Math.pow(10, c);
  25. if (r1 > r2) {
  26. arg1 = Number(arg1.toString().replace(".", ""));
  27. arg2 = Number(arg2.toString().replace(".", "")) * cm;
  28. } else {
  29. arg1 = Number(arg1.toString().replace(".", "")) * cm;
  30. arg2 = Number(arg2.toString().replace(".", ""));
  31. }
  32. } else {
  33. arg1 = Number(arg1.toString().replace(".", ""));
  34. arg2 = Number(arg2.toString().replace(".", ""));
  35. }
  36. return (arg1 + arg2) / m;
  37. }
  38.  
  39. // Number add , 。
  40. Number.prototype.add = function (arg) {
  41. return accAdd(arg, this);
  42. };
減算関数
JavaScript :
  1. /**
  2. ** ,
  3. ** :javascript , 。 。
  4. ** :accSub(arg1,arg2)
  5. ** :arg1 arg2
  6. **/
  7. function accSub(arg1, arg2) {
  8. var r1, r2, m, n;
  9. try {
  10. r1 = arg1.toString().split(".")[1].length;
  11. }
  12. catch (e) {
  13. r1 = 0;
  14. }
  15. try {
  16. r2 = arg2.toString().split(".")[1].length;
  17. }
  18. catch (e) {
  19. r2 = 0;
  20. }
  21. m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //
  22. n = (r1 >= r2) ? r1 : r2;
  23. return ((arg1 * m - arg2 * m) / m).toFixed(n);
  24. }
  25.  
  26. // Number mul , 。
  27. Number.prototype.sub = function (arg) {
  28. return accMul(arg, this);
  29. };
乗算関数
JavaScript :
  1. /**
  2. ** ,
  3. ** :javascript , 。 。
  4. ** :accMul(arg1,arg2)
  5. ** :arg1 arg2
  6. **/
  7. function accMul(arg1, arg2) {
  8. var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
  9. try {
  10. m += s1.split(".")[1].length;
  11. }
  12. catch (e) {
  13. }
  14. try {
  15. m += s2.split(".")[1].length;
  16. }
  17. catch (e) {
  18. }
  19. return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
  20. }
  21.  
  22. // Number mul , 。
  23. Number.prototype.mul = function (arg) {
  24. return accMul(arg, this);
  25. };
除法関数
JavaScript :
  1. /**
  2. ** ,
  3. ** :javascript , 。 。
  4. ** :accDiv(arg1,arg2)
  5. ** :arg1 arg2
  6. **/
  7. function accDiv(arg1, arg2) {
  8. var t1 = 0, t2 = 0, r1, r2;
  9. try {
  10. t1 = arg1.toString().split(".")[1].length;
  11. }
  12. catch (e) {
  13. }
  14. try {
  15. t2 = arg2.toString().split(".")[1].length;
  16. }
  17. catch (e) {
  18. }
  19. with (Math) {
  20. r1 = Number(arg1.toString().replace(".", ""));
  21. r2 = Number(arg2.toString().replace(".", ""));
  22. return (r1 / r2) * pow(10, t2 - t1);
  23. }
  24. }
  25.  
  26. // Number div , 。
  27. Number.prototype.div = function (arg) {
  28. return accDiv(this, arg);
  29. };
原文:http://www.css88.com/archives/7340#more-7340