JavaScriptでの正確な財務計算。落とし穴とは何ですか?


125

クロスプラットフォームコードを作成するために、JavaScriptで簡単な金融アプリケーションを開発したいと思います。必要な計算には複利と比較的長い10進数が含まれます。JavaScriptを使用してこのタイプの数学を実行する場合に避けられるミスを教えてください—可能であれば!

回答:


107

おそらく、10進数の値を100倍にして、すべての金額をセント単位で表す必要があります。これは、浮動小数点論理と算術の問題を回避するためです。JavaScriptには10進データタイプはありません。数値データタイプは浮動小数点のみです。したがって、通常は255025.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)


3
そして注意として、計算は常にセントに丸め、消費者にとっては最も有益でない方法で行います。IE税金を計算している場合は、切り上げます。取得した利息を計算する場合は、切り捨てます。
ジョシュ

6
@Cirrostratus:stackoverflow.com/questions/744099を確認してください。スケーリング方法を進める場合、一般的には、精度を維持したい10進数の桁数で値をスケーリングする必要があります。あなたが4、スケール10000によって必要な場合は、100で小数点以下2桁、スケールが必要な場合
ダニエルVassallo氏

2
... 3000.57の値については、そうです。その値をJavaScript変数に格納し、それに対して算術演算を行うつもりであれば、300057(セントの数)にスケーリングして格納することをお勧めします。3000.57 + 0.11 === 3000.68戻るのでfalse
Daniel Vassallo、

7
ドルの代わりにペニーを数えるのは役に立ちません。 ペニーを数える場合、約10 ^ 16の整数に1を追加する機能を失います。ドルを数える場合、10 ^ 14の数値に.01を追加する機能を失います。どちらでも同じです。

1
このタスクにうまくいけばnpmおよびBowerモジュールを作成したところです。
Pensierinmusica 2014

18

すべての値を100でスケーリングすることがソリューションです。手でそれを行うライブラリを見つけることができるので、手でそれを行うことはおそらく役に立たないです。ES6アプリケーションに適した機能APIを提供するmoneysafeをお勧めします。

const { in$, $ } = require('moneysafe');
console.log(in$($(10.5) + $(.3)); // 10.8

https://github.com/ericelliott/moneysafe

Node.jsとブラウザの両方で機能します。


2
賛成。「100倍のスケール」ポイントはすでに受け入れ済みの回答でカバーされていますが、最新のJavaScript構文を使用してソフトウェアパッケージオプションを追加したことは良いことです。FWIW in$, $ 値の名前は、パッケージを以前に使用したことがない人にはあいまいです。そのように名前を付けるのはエリックの選択だったのはわかっていますが、インポート/非構造化requireステートメントで名前を変更するのは間違いであることは十分に感じます。
james_womack 2017

4
100倍のスケーリングは、パーセンテージの計算(基本的には除算を行う)などの処理を開始するまでの助けになります。
2018年

2
コメントを複数回投票できるといいのですが。100だけのスケーリングでは不十分です。JavaScriptの唯一の数値データ型は依然として浮動小数点データ型であり、重大な丸めエラーが発生することになります。
クレイグ

1
もう1つは、Readmeからの見出しですMoney$afe has not yet been tested in production at scale.。誰もがその後、それはそれらを使用する場合に適していた場合に考慮することができますので、ちょうどそのアウトを指して
のび太

7

小数点以下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オブジェクトが両方の値を保持するという事実は非常に便利です。これは、カスタムObjective-Cクラスで作成したい機能のタイプです。
james_womack

4
四捨五入するほど正確ではありません。
Henry Tseng 2013年

3
sys.puts(m + n); //80.76は実際にsys.puts(m + n);を読み取ります。//80.77?.5を切り上げるのを忘れたと思います。
Dave L

2
この種のアプローチには、発生する可能性のあるいくつかの微妙な問題があります。たとえば、足し算、引き算、掛け算などの安全な方法を実装していないため、金額を組み合わせるときに丸め誤差が発生する可能性があります
1800 INFORMATION

2
ここでの問題は、たとえばMoney(0.1)、JavaScriptレクサーがソースから文字列「0.1」を読み取り、それを2進浮動小数点に変換し、その後、意図しない丸めを既に行ったことを意味します。問題は、精度ではなく表現(バイナリと10進数)に関するものです。
mgd 2015年


2

問題は、浮動小数点計算の不正確さに起因します。これを解決するために丸めを使用しているだけの場合、乗算と除算を行うときに大きなエラーが発生します。

解決策は次のとおりです。説明は次のとおりです。

これを理解するには、この背後にある数学について考える必要があります。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/


1

エンコードのバイナリの性質により、一部の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);

JavaScriptでの10進数学の問題の回避

優れたドキュメントを備えた財務計算専用のライブラリがあります。Finance.js


私は、Finance.jsにもサンプルアプリがあることを気に入っています
james_womack

0

残念ながら、これまでのすべての回答では、すべての通貨が100のサブユニットを持っているわけではないという事実を無視しています(たとえば、セントは米ドル(USD)のサブユニットです)。イラクディナール(IQD)のような通貨には1000のサブユニットがあります。イラクディナールには1000フィルがあります。日本円(JPY)にはサブユニットがありません。したがって、「100を掛けて整数演算を行う」とは、必ずしも正しい答えとは限りません。

さらに、通貨の計算では、通貨を追跡する必要もあります。インドルピー(INR)に米ドル(USD)を追加することはできません(最初に一方を他方に変換しないと)。

JavaScriptの整数データ型で表すことができる最大量にも制限があります。

通貨の計算では、お金の精度が有限(通常0から3小数点)であり、丸めは特定の方法で行う必要がある(たとえば、「通常の」丸めと銀行の丸めの比較など)ことにも注意する必要があります。実行される丸めのタイプも、管轄/通貨によって異なる場合があります。

JavaScriptでお金を処理する方法は、関連するポイントについて非常に良い議論があります。

私の検索で、金額計算に関する多くの問題に対処するdinero.jsライブラリを見つけました。まだ運用システムで使用していないため、十分な情報に基づく意見を述べることはできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.