やっていくログ

にんげんをやっていきましょう

【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倍の開きがあった。計算がより複雑になると変わってくるかもしれないが、まあメチャクチャ遅いって感じはしない。