[iOS swift] Doubleとか小数の浮動小数点の誤差をなくしたい NSDecimalNumber

2016/03/8

こんにちは。きんくまです。

電卓アプリのZippy電卓を作っていて浮動小数点の誤差が出ることに気がつきました。

例えばこんな計算をやってみます。

2.35 – 2.34 = 0.01 になるはずです。

let n1:Double = 2.35
let n2:Double = 2.34
let n3:Double = n1 - n2
print("\(n3)")

これの出力結果はなんと、、、、

0.0100000000000002

という数値が出ます。
これはたぶんなんですが、小数を表すときに内部的に誤差が出てしまうからだとおもいます。

で、これを防ぐのにはNSDecimalNumberを使います。

let d1:NSDecimalNumber = NSDecimalNumber(double: 2.35)
let d2:NSDecimalNumber = NSDecimalNumber(double: 2.34)
let d3:NSDecimalNumber = d1.decimalNumberBySubtracting(d2)
print("\(d3.stringValue)") //0.01

端数処理でつまった

それで、ここまではOKだったのですが、先日つけた端数処理(丸め)機能でつまりました。

NSDecimalNumberを丸めるのには、NSDecimalNumberHandlerを使えばいいみたいです。やってみましょう。

let r0:NSDecimalNumber = NSDecimalNumber(double: 1.235)

let behaviors:NSDecimalNumberHandler = NSDecimalNumberHandler(
    roundingMode: NSRoundingMode.RoundUp,
    scale: 3,
    raiseOnExactness: false,
    raiseOnOverflow: false,
    raiseOnUnderflow: false,
    raiseOnDivideByZero: false)

let r1:NSDecimalNumber = r0.decimalNumberByRoundingAccordingToBehavior(behaviors)
print("\(r1.stringValue)") 

NSDecimalNumberHandler は roundingMode で 四捨五入 / 切り上げ / 切り捨て を行います。
scaleで小数点以下何桁にするかです。

上の例だと 1.235 を小数点第3位で切り上げるものです。
数学的にはその後ろの数値が全くないのでそのまま、1.235 と出そうです。
ところが、この結果は

1.236

という結果が出ます。おっと!

で、これをどうやって直すかというと、最初の部分のr0を数値ではなくて文字列にします。

let r0:NSDecimalNumber = NSDecimalNumber(string: "1.235")

すると、結果はきちんと

1.235

と想定したものになりました!やったー。

で、自分の場合はこれすぐに文字列にする解決方法が思いついたのです。なんでかといいますと、Zippy電卓には元となったJavaScript版がありまして、そこでも同じことがおきていたからなんです。

JavaScript版の記事にも書いたのですが、これの小数の計算もBig.jsというライブラリを使って文字列で数値を渡して計算していたんです。

>> [JavaScript] スマホで使えるJavaScriptの電卓作った

つまり何がいいたいかというと、言語にかかわらず、小数というのは変数に数値型で入れたときに誤差がすでに出やすくなるっていうことなのでは?ということです。(すみません、確実な証拠がないんで言い切れないところが情けない、、)

だから、小数は文字列におきかえて「凍結」してしまえば、計算や通信する際に誤差を生じさせにくくできるのかなーと。
そんな感じです。ではでは。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る