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

Swift

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

Decimal の特徴

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

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

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

浮動小数点として使われる Double 等での注意点とは少し異なるのですが、後半で関係してくるので、浮動小数点での注意点から説明していきます。

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 は、10進数を使用して計算してくれるので、2の冪乗からくる誤差は考慮する必要がありません。

最初にも書きましたが、Decimal での計算で気をつけるべき点は、値が取る範囲です。

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

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

Decimal を使ってみる

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

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

なんと、上記で適用される Decimal の初期化関数は、init(Double) です。

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

つまり、100.0040 を一度 Double に変換してから、Decimal にしています。この最初の Double への変換で誤差が発生しているのですが、Swift は、ていねいに誤差も含めて Decimal へ変換しています。

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

Decimal を正しく初期化する

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

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

Github 等を覗いてみるといくつか見つかります。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

Swift おすすめ本

Swift を深く理解するには、以下の本がおすすめです。

コメントを残す

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