2017/09/07更新

[Python] 少数を含む金額計算を正確に行う(decimalモジュールの利用)

このエントリーをはてなブックマークに追加            

こんにちは、@yoheiMuneです。
最近はシステムトレードのプログラムを書いていて、金額計算などで少数を正確に扱う必要があり、decimalモジュールを利用しました。今日のブログではそのDecimalの使い方をブログに残したいと思います。
画像

目次




decimalモジュールとは

decimalモジュールは、少数を含む数値を正確に表現したり計算したりすることに使えるモジュールです。

パソコンでは数値を2進数で表現しますが、2進数では0.1という表現(1の10分の1)を正確に表すことができません。
num = 1.1
print(type(num)) # <class 'float'>  => float(浮動点少数で扱っている)
参考:1より小さい数を含む二進数表現

なんでもない数値を扱うときは、2進数で表現できない数値の誤差は気にしないのですが、お金とかを扱う場合には正しく扱いたいものです。その際にdecimalモジュールを使うことで、0.1などを正確に扱うことができるようになります。



decimalモジュールを使って、0.1を表現する

decimalモジュールを使うと、10進数の0.1を浮動点少数(floating point)ではなく、固定点少数(fixed point)で扱うことができます。
# decimalで0.1を表現する方法
from decimal import Decimal
num = Decimal("0.1")
print(num)   # 0.1
ここでポイントとしては、"0.1"と文字列で指定することで、固定点少数で扱うことができます。0.1とfloat型で指定すると、decimalは浮動点少数で値を保持します。
# decimalで浮動点少数で0.1を表現する
from decimal import Decimal
num = Decimal(0.1)
print(num)  # 0.1000000000000000055511151231257827021181583404541015625
固定点少数の場合と、保持している数値が異なることがわかります。ということで、0.1を正確に扱うためにはDecimal("0.1")とする必要があります。



decimalでの演算

Decimalクラスには演算子が定義されているので、普通の数値のように演算することができます。
from decimal import Decimal

# Decimalで演算をする例
Decimal("0.1") + Decimal("0.2") # Decimal('0.3')
Decimal("0.1") - Decimal("0.2") # Decimal('-0.1')
Decimal("0.1") * Decimal("0.2") # Decimal('0.02')
Decimal("0.1") / Decimal("0.2") # Decimal('0.5')


演算結果の精度を指定する

また、演算した結果、どれだけの精度で保持するかを指定することもできます。
from decimal import Decimal, getcontext

# デフォルトでは少数28桁まで保持する.
Decimal(1) / Decimal(7) # Decimal('0.1428571428571428571428571429')

# 精度を指定することもできる(以下では少数第3位まで).
getcontext().prec = 3
Decimal(1) / Decimal(7) # Decimal('0.143')


floatトラップで安全に計算する

そのほかに、安全のため、Decimalの計算で、うっかりfloatを混ぜないように(混ぜたらエラーになるように)設定することができます。
from decimal import Decimal, getcontext, FloatOperation

# floatトラップを有効にする
getcontext().traps[FloatOperation] = True

# floatで初期化するとエラー
Decimal(3.14)
# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# decimal.FloatOperation: [<class 'decimal.FloatOperation'>]

# floatが演算に混ざるとエラー
Decimal(2) < 0.5
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# decimal.FloatOperation: [<class 'decimal.FloatOperation'>]


数値の丸め方を指定する

制度からはみ出す数値をどのように丸めるのか、これも金額計算では重要なポイントです(例えば日本で消費税は小数点以下を切り捨てる、など)。
演算した結果の数値を丸める方は、以下のように指定することができます。
from decimal import Decimal, getcontext, ROUND_UP

# 有効桁数を3
getcontext().prec = 3

# デフォルトでは、ROUND_HALF_EVEN(近い方に、引き分けは偶数整数方向に向けて丸めます)
getcontext().rounding # ROUND_HALF_EVEN
Decimal(1) / Decimal(3) # Decimal('0.333')

# 例えば、ROUND_FLOOR(ゼロから遠い方向に丸めます。)に変更できます.
getcontext().rounding = ROUND_UP
Decimal(1) / Decimal(3) # Decimal('0.334')
丸め方のそのほかの指定は、「ドキュメント - 9.4.5. 丸めモード」をご確認ください。



参考資料

Pythonの公式ドキュメントが非常に参考になりますので、詳細はそちらをご確認ください。

https://docs.python.org/ja/3/library/decimal.html



最後に

金額計算の実装はなかなかスリリングですが、正確にできると楽しいですね。人のお金を扱うプログラミングはまだまだ書ける気がしませんが、自分のお金を扱いつつパワーアップできたらと思います。今後もPythonはドシドシと取り組んでいきたい言語です。

最後になりますが本ブログでは、Python・Linux・フロントエンド・Node.js・インフラ・開発環境・Go言語・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。