【Python】浮動小数点処理とdecimalモジュール
・はじめに
cryptocurrency bot制作でしばらく悩まされていた不具合の原因が小数計算にあることに気づき、Pythonにおける浮動小数点処理を調べる必要に迫られた。decimalモジュールを利用する際の備忘録として以下にまとめる。付け焼刃なので間違っているかも。
・とりあえず使うために
まずはインポート
from decimal import *
Decimal()の引数に数値か文字列を与える。数値で与えるとfloatと同じ。文字列で与えると10進数同様の取り扱いができる。こうすれば四則演算は普通に行ってよい。
a=Decimal(0.1) #floatとおなじ b=Decimal('0.1') #10進数扱い c=0.1 #float d=Decimal(str(c)) #10進数扱い
丸め処理は.quantize()を用いる。roundingを指定することでいくつかの丸め処理が可能。とりあえずゼロ丸めだけしたかったのでその例のみ記す。quantizeの第1パラメータには揃えたい桁数を有する値をDecimal形式で与える。1E-4などで与えてもよい。第2パラメータにはroundingを指定する。指定しなければDecimalのデフォルトの丸め形式が実行される。
Decimal('0.01283').quantize(Decimal('0.01'),rounding=ROUND_DOWN) #0.01 Decimal('0.314155').quantize(Decimal('1E-2'),rounding=ROUND_DOWN) #0.31
一つの丸め処理しかしない場合はデフォルトの丸め形式を指定しておくと楽そう。またデフォルトの有効桁数を指定することもできるようである。
getcontext().prec=24 #24桁にする print(Decimal('0.00000038471942')/Decimal('0.0000312413')) #0.0123144497828195369590894 getcontext().rounding=ROUND_DOWN #切り捨てに設定 getcontext().prec=10 #10桁にする print(Decimal('0.00000038471942')/Decimal('0.0000312413')) #0.01231444978
なお、decimalで得た数値を他の関数に与えるなどの場合はintやfloatにキャストしてあげないといけない。
・速度を調べる
floatで計算したときより速度が遅いらしいので、雑に速度を比較した。以下のようにひたすら小数を足し上げるのにかかる時間を測定。
import time from decimal import * start=time.time() sum1=Decimal('0') for i in range(1000): sum1+=Decimal('0.1111') end=time.time() print(sum1,end-start) start2=time.time() sum2=0 for i in range(1000): sum2+=0.1111 end2=time.time() print(sum2,end2-start2)
値と実行時間の結果は以下の通り。
float合計 | decimal合計 | float実行時間 | decimal実行時間 | |
100回 | 11.110000000000026 | 11.1100 | 0.0009992123 | 0.0009994507 |
1000回 | 111.0999999999974 | 111.1000 | 0.0010001659 | 0.0009992123 |
10000回 | 1110.9999999999386 | 1111.0000 | 0.0019972324 | 0.0049958229 |
100000回 | 11110.00000000701 | 11110.0000 | 0.0129878521 | 0.0489549637 |
1000000回 | 111099.99999810797 | 111100.0000 | 0.1228837967 | 0.4615705013 |
10000000回 | 1110999.9999210974 | 1111000.0000 | 1.1908910275 | 4.4668421745 |
単純な足し算だからか、実行時間はほぼ線形に増加した。floatで扱ったときとdecimalで扱った時では約4倍の開きがあった。計算がより複雑になると変わってくるかもしれないが、まあメチャクチャ遅いって感じはしない。