クロスプラットフォームコードを作成するために、JavaScriptで簡単な金融アプリケーションを開発したいと思います。必要な計算には複利と比較的長い10進数が含まれます。JavaScriptを使用してこのタイプの数学を実行する場合に避けられるミスを教えてください—可能であれば!
クロスプラットフォームコードを作成するために、JavaScriptで簡単な金融アプリケーションを開発したいと思います。必要な計算には複利と比較的長い10進数が含まれます。JavaScriptを使用してこのタイプの数学を実行する場合に避けられるミスを教えてください—可能であれば!
回答:
おそらく、10進数の値を100倍にして、すべての金額をセント単位で表す必要があります。これは、浮動小数点論理と算術の問題を回避するためです。JavaScriptには10進データタイプはありません。数値データタイプは浮動小数点のみです。したがって、通常は2550
、25.50
ドルではなくセントとしてお金を処理することをお勧めします。
JavaScriptでそれを考えてください:
var result = 1.0 + 2.0; // (result === 3.0) returns true
だが:
var result = 0.1 + 0.2; // (result === 0.3) returns false
式0.1 + 0.2 === 0.3
はを返しますがfalse
、幸い、浮動小数点での整数演算は正確であるため、1をスケーリングすることで10進数表現のエラーを回避できます。
実数のセットは無限大ですが、JavaScript浮動小数点形式で正確に表現できるのは有限数(正確には、18,437,736,874,454,810,627)のみであることに注意してください。したがって、他の数値の表現は実際の数値の近似値になります2。
1ダグラス・クロックフォード:JavaScript:良い部分:付録A-ひどい部分(105ページ)。
2 David Flanagan:JavaScript:The Definitive Guide、Fourth Edition:3.1.3 Floating-Point Literals(page 31)。
3000.57 + 0.11 === 3000.68
戻るのでfalse
。
すべての値を100でスケーリングすることがソリューションです。手でそれを行うライブラリを見つけることができるので、手でそれを行うことはおそらく役に立たないです。ES6アプリケーションに適した機能APIを提供するmoneysafeをお勧めします。
const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8
https://github.com/ericelliott/moneysafe
Node.jsとブラウザの両方で機能します。
in$, $
値の名前は、パッケージを以前に使用したことがない人にはあいまいです。そのように名前を付けるのはエリックの選択だったのはわかっていますが、インポート/非構造化requireステートメントで名前を変更するのは間違いであることは十分に感じます。
Money$afe has not yet been tested in production at scale.
。誰もがその後、それはそれらを使用する場合に適していた場合に考慮することができますので、ちょうどそのアウトを指して
小数点以下2桁だけのため、「正確な」財務計算などはありませんが、それはより一般的な問題です。
JavaScriptでは、すべての値を100でスケーリングしMath.round()
、端数が発生するたびに使用できます。
オブジェクトを使用して数値を格納し、そのプロトタイプvalueOf()
メソッドに丸めを含めることができます。このような:
sys = require('sys');
var Money = function(amount) {
this.amount = amount;
}
Money.prototype.valueOf = function() {
return Math.round(this.amount*100)/100;
}
var m = new Money(50.42355446);
var n = new Money(30.342141);
sys.puts(m.amount + n.amount); //80.76569546
sys.puts(m+n); //80.76
このようにして、Moneyオブジェクトを使用するたびに、小数点以下2桁に丸められて表されます。丸められていない値は、を介して引き続きアクセスできますm.amount
。
Money.prototype.valueOf()
必要に応じて、独自の丸めアルゴリズムをに組み込むことができます。
Money(0.1)
、JavaScriptレクサーがソースから文字列「0.1」を読み取り、それを2進浮動小数点に変換し、その後、意図しない丸めを既に行ったことを意味します。問題は、精度ではなく表現(バイナリと10進数)に関するものです。
decimaljsを使用してください...問題の厳しい部分を解決する非常に優れたライブラリです...
すべての操作で使用してください。
問題は、浮動小数点計算の不正確さに起因します。これを解決するために丸めを使用しているだけの場合、乗算と除算を行うときに大きなエラーが発生します。
解決策は次のとおりです。説明は次のとおりです。
これを理解するには、この背後にある数学について考える必要があります。1/3のような実数は無限であるので、10進値を使用して数学で表すことはできません(例:-.333333333333333 ...)。10進数の一部の数値は、2進数で正しく表現できません。たとえば、0.1は限られた桁数のバイナリでは正しく表現できません。
詳細については、こちらをご覧ください:http : //docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
ソリューションの実装を見て ください:http : //floating-point-gui.de/languages/javascript/
エンコードのバイナリの性質により、一部の10進数は完全な精度で表現できません。例えば
var money = 600.90;
var price = 200.30;
var total = price * 3;
// Outputs: false
console.log(money >= total);
// Outputs: 600.9000000000001
console.log(total);
純粋なJavaScriptを使用する必要がある場合は、すべての計算のソリューションについて考える必要があります。上記のコードでは、10進数を整数に変換できます。
var money = 60090;
var price = 20030;
var total = price * 3;
// Outputs: true
console.log(money >= total);
// Outputs: 60090
console.log(total);
優れたドキュメントを備えた財務計算専用のライブラリがあります。Finance.js
残念ながら、これまでのすべての回答では、すべての通貨が100のサブユニットを持っているわけではないという事実を無視しています(たとえば、セントは米ドル(USD)のサブユニットです)。イラクディナール(IQD)のような通貨には1000のサブユニットがあります。イラクディナールには1000フィルがあります。日本円(JPY)にはサブユニットがありません。したがって、「100を掛けて整数演算を行う」とは、必ずしも正しい答えとは限りません。
さらに、通貨の計算では、通貨を追跡する必要もあります。インドルピー(INR)に米ドル(USD)を追加することはできません(最初に一方を他方に変換しないと)。
JavaScriptの整数データ型で表すことができる最大量にも制限があります。
通貨の計算では、お金の精度が有限(通常0から3小数点)であり、丸めは特定の方法で行う必要がある(たとえば、「通常の」丸めと銀行の丸めの比較など)ことにも注意する必要があります。実行される丸めのタイプも、管轄/通貨によって異なる場合があります。
JavaScriptでお金を処理する方法は、関連するポイントについて非常に良い議論があります。
私の検索で、金額計算に関する多くの問題に対処するdinero.jsライブラリを見つけました。まだ運用システムで使用していないため、十分な情報に基づく意見を述べることはできません。