web-dev-qa-db-ja.com

お金を処理するためにNSDecimalNumberを使用する必要がありますか?

私が最初のアプリのコーディングを始めたとき、私は二度考えずにお金の価値にNSNumberを使用しました。それから、おそらくc型で私の値を処理するのに十分だと思いました。それでも、iPhone SDKフォーラムでは、優れた丸め機能があるため、NSDecimalNumberを使用するようにアドバイスされました。

気質によって数学者ではないので、仮数/指数パラダイムはやり過ぎかもしれないと思いました。それでも、グーグリンの周りで、私はココアのお金/通貨についてのほとんどの話がNSDecimalNumberに言及されていることに気づきました。

私が取り組んでいるアプリは国際化される予定であるため、金額をセントでカウントするオプションは実際には実行可能ではないことに注意してください。金銭的構造は使用するロケールに大きく依存するためです。

NSDecimalNumberを使用する必要があると90%確信していますが、Web上で明確な答えが見つからなかったため(「お金を扱う場合はNSDecimalNumberを使用してください!」など)、ここで質問したいと思いました。答えはほとんどの人にとって明白かもしれませんが、アプリの大規模なリファクタリングを開始する前に確認したいと思います。

信じさせて :)

52
nutsmuggler

Marcus Zarraは、これについてかなり明確なスタンスを持っています: "通貨を扱っている場合は、NSDecimalNumberを使用する必要があります。" 彼の記事は、NSDecimalNumberを調べるように私を刺激しました。それに感銘を受けました。基数10の数学を処理するときのIEEE浮動小数点エラーはしばらくの間私を苛立たせていました(1 *(0.5-0.4-0.1)= -0.00000000000000002776)そしてNSDecimalNumberはそれらを取り除きます。

NSDecimalNumberは、2進数の浮動小数点精度をさらに数桁追加するだけでなく、実際には基数10の計算を行います。これにより、上記の例に示されているようなエラーが取り除かれます。

今、私は記号計算アプリケーションを書いているので、10進数の30以上の精度と、奇妙な浮動小数点エラーがないことを望んでいることも例外かもしれませんが、一見の価値があると思います。操作は、単純なvar = 1 + 2スタイルの数学よりも少し厄介ですが、それでも管理可能です。数学演算中にあらゆる種類のインスタンスを割り当てることを心配している場合、NSDecimalはNSDecimalNumberと同等のC構造体であり、まったく同じ数学演算を実行するためのC関数があります。私の経験では、これらは最も要求の厳しいアプリケーション(MacBook Airでは3,344,593追加/秒、254,017分割/秒、iPhoneでは281,555追加/秒、12,027分割/秒)を除いてすべて高速です。

追加のボーナスとして、NSDecimalNumberのdescriptionWithLocale:メソッドは、正しい小数点記号を含む、数値のローカライズされたバージョンを含む文字列を提供します。 initWithString:locale:メソッドについても同じことが逆になります。

63
Brad Larson

はい。あなたは使用する必要があります

NSDecimalNumberおよび

iOSで通貨を扱う場合は、doubleまたはfloatではありません。

何故ですか??

$ 10の代わりに$ 9.9999999998のようなものを取得したくないため

それはどのように起こりますか?

フロートとダブルは概算です。それらには常に丸め誤差が伴います。コンピュータが小数を格納するために使用する形式は、このルーティングエラーを引き起こします。詳細が必要な場合はお読みください

http://floating-point-gui.de/

Apple docsによると、

NSDecimalNumberはNSNumberの不変のサブクラスであり、基数10の演算を実行するためのオブジェクト指向ラッパーを提供します。インスタンスは、仮数x 10 ^指数として表すことができる任意の数値を表すことができます。ここで、仮数は最大38桁の長さの小数整数であり、指数は–128から127までの整数です。10を底とする算術演算を行うためのラッパー。

したがって、NSDecimalNumberは通貨を扱うために一般化されています。

8
MadNik

(他の回答に対する私のコメントから適応。)

はい、そうすべきです。整数のペニーは、たとえば0.5セントを表す必要がない場合にのみ機能します。その場合は、0.5セントをカウントするように変更できますが、4分の1セント、つまり8分の1セントを表す必要がある場合はどうでしょうか。

唯一の適切な解決策はNSDecimalNumber(またはそのようなもの)であり、これは問題を10 ^ -128¢(つまり、
0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000001¢)。

(別の方法は任意精度の演算ですが、 GNU MP Bignum library 。GMPはLGPLの下にあります。私はそのライブラリを使用したことがなく、どのように機能するか正確にわからないため、どの程度うまく機能するかはわかりませんでした。)

[編集:どうやら、少なくとも1人の人(Brad Larson)は、この回答のどこかでバイナリ浮動小数点について話していると思っています。私は違います。]

5
Peter Hosey

より良い質問は、いつnotNSDecimalNumberを使用してお金を処理する必要があるかということです。その質問に対する簡単な答えは、NSDecimalNumberのパフォーマンスのオーバーヘッドを許容できず、数桁を超える精度を処理することがないため、小さな丸め誤差を気にしない場合です。さらに短い答えは、お金を扱うときは常に NSDecimalNumberを使用する必要があるということです。

4
Tony

VISA、MasterCardsなどは、金額を渡す際に整数値を使用しています。通貨の指数に従って金額を正しく解析するのは送信者と受信者の責任です(10 ^ numで除算または乗算します。ここで、num-は通貨の指数です)。通貨が異なれば指数も異なることに注意してください。通常は2です(したがって、100で除算して乗算します)が、一部の通貨の指数は0(VNDなど)または= 3です。

2

セント数を表すのに整数を使用し、次に100で割って表示すると便利だと思いました。問題全体を回避します。

2
wisequark