[Swift] Decimal の初期化には注意が必要

Swift

お金に関わる計算には、Double ではなく、Decimal を使った方が良いです。
ですが、きちんと使わないと、精度の問題が発生します。
Decimal を使うときに、気をつけなければいけない点を説明します。

Decimal の特徴

固定小数点を使って表現されているので、計算誤差は起こりません。

具体的には、10の累乗を使用して表現しているので、10進数での計算では、誤差が起こりません。

ただし、計算途中であっても、表現可能な範囲から外れてしまうと、計算がおかしくなります。

Double とその注意点

Double では、2進数で表現されますので、2の累乗を使って表現していることになります。

例えば、5 = 2^2 + 1 となります。

整数であれば、このように綺麗に表現できるのですが、小数部は、1/2, (1/2)^2, … を使って表現しなくてはならず、

例えば、0.75 = 1/2 + (1/2)^2 となります。

0.75 は、綺麗に表現できたのですが、これが、0.76 とすると 0.1 についても、2の冪乗で表現死なれけばいけないので、
0.1 = (1/2)^4 + ….. となり、近似値の処理となってしまいます。 これが誤差の発生する理由となります。

浮動小数点表現を使用するタイプは、他に Float もありますが、有効桁数が違うだけで動作原理は、同じです。

Decimal で気をつけなければいけない点

Decimal での計算は、その範囲を気をつける点のみです。

落とし穴は、変数を設定する時になります。

例えば、以下を Playground で実行してみます。

Decimal を使ってみる

100.004 になって欲しいのですが、100.00400000000002048 になってしまっています。

その理由は、Decimal の 初期化関数をみるとわかります。

なんと、Decimal の初期化関数は、init(Double) なんです。

Apple のドキュメントは、こちら

つまり、100.0040 を Double に変換してから、Decimal にしているんです。この最初の Double への変換で誤差が発生しています。

この誤差を発生させないためには、10進数できちんと表せる表現で設定することが必要です。

Decimal を正しく初期化する

計算途中の誤差には、気をつけていると思いますが、このような箇所の落とし穴が盲点です。

注意
ここでの誤差は、無限小数を表現するときの差異を含めていません。
例えば、10 / 3 = 3.333333・・・ となりますので、無限小数となりますが、この小数を誤差なく表現することは、Decimal, Double, Float いずれでもできません。
正確に表現するためには、有理数というタイプが必要ですが、Swift の標準タイプとしては、採用されていません。

Github 等を除くといくつか見つかります。

説明は以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です