Decimalオブジェクトはpythonで10進数での演算をおこなう際に用いるオブジェクトです。PCやサーバを含め、コンピュータは通常、数を2進数で処理します。しかし小数が入ってくる計算では2進数と10進数で誤差が発生します。そのため金額の計算などは10進数を用いて計算する必要があります。ここではpythonで10進数の計算をおこなうためのDecimalオブジェクトについて述べます。
Decimalオブジェクトとは
pythonで10進数の演算をおこなうためのオブジェクトです。初期化は10進数表記した数字の文字列を与えることでおこないます。
from decimal import Decimal
d1=Decimal('99.999999')
print(d1)
実行結果
99.999999
Decimalオブジェクトの利点
利点は10進数演算を指定された精度で実行できることです。 もちろん実際には法律やシステムで要求された桁数で丸めることになりますがそれは仕様上決めたことです。 そしてDecimalでは精度を指定して計算する方法を機能として保持しています。
例えば小数第2桁まで精度を保持して計算したいとします。その場合以下のようになります。
from decimal import Decimal, getcontext
# 小数点が2桁になるように丸める
getcontext().prec=2
d1=Decimal('1.0')
d2=Decimal('3.0')
d3=d1/d2
print(d3)
実行結果
0.33
getcontext().prec=2で計算結果が小数2桁の精度で丸められます。実際にd3=1.0/3.0をおこなっていますが小数3桁目が丸められ結果は小数2桁になっています。
Decimalオブジェクトの欠点
Decimalオブジェクトの欠点としてはfloatに比べ計算速度が遅いことです。実際にループを回して確かめてみます。
from decimal import Decimal, ROUND_HALF_EVEN, getcontext
from datetime import datetime
getcontext().rounding=ROUND_HALF_EVEN
getcontext().prec=17
dtnow=datetime.now()
f=1.0
f1=1.0
for i in range(1,1000000):
f1=f1+(f/float(i))
print(f1)
dtend=datetime.now()
time_float=dtend-dtnow
print(time_float)
dtnow=datetime.now()
d=Decimal('1.0')
d1=Decimal('1.0')
for i in range(1,1000000):
d1=d1+(d/Decimal(str(i)))
print(d1)
dtend=datetime.now()
time_float=dtend-dtnow
print(time_float)
実行結果
15.392725722865015 0:00:00.183005 15.392725722865609 0:00:00.808550
この例では処理速度が8倍程度異なります(できるかぎり公平にするためDecimalの精度をfloatと同程度に下げています)。
偶数丸め、JIS丸め、銀行丸め
pythonは数を丸めるときに四捨五入するわけではありまん。例えば0.5は0と1の間にある値です。四捨五入であれば1になるはずです。しかしpythonはJIS丸めや偶数丸め、銀行丸めと呼ばれる方法で数字を丸めます。丸める値が5の場合一つ上の桁が偶数になるように丸めます。
たとえば0.5を丸めて整数にする場合は以下のようになります。
from decimal import Decimal, getcontext
d1=Decimal('0.5')
print(d1)
# 小数部を丸める
print(d1.quantize(Decimal('0')))
実行結果
0.5 0
小数第1位を丸めます。小数第1位は5です。ですので一つ上の桁(整数の1の位)が偶数になるように丸めます。結果として0.5は0に丸められます。
1.5の小数部をまとめる場合、小数第1位は5です。ですので一つ上の桁(整数の1の位)が偶数になるように丸めます。結果として1.5は2に丸められます。
なぜ偶数丸めを使うのか
四捨五入でなく偶数丸めを使うのかは理由があります。結論を言うと四捨五入や切り上げ、切り下げよりも偶数丸めのほうが計算精度が高くなるからです。
例として体積を直方体の体積を求める問題を考えます。直方体の長さ、幅、高さがそれぞれ以下のようにあたえられているとします。
mm | |
---|---|
長さ | 13.5 |
幅 | 10.5 |
高さ | 11.5 |
これをそれぞれの丸め方法で計算してみます。精度はmmで丸められた後計算され、計算後も丸めるものとします。
from decimal import Decimal, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP
length=Decimal('13.5')
width=Decimal('10.5')
height=Decimal('11.1')
# 誤差なし
volume=length*width*height
# 体積 #偶数丸め
length1=length.quantize(Decimal('0'), rounding=ROUND_HALF_EVEN)
width1=width.quantize(Decimal('0'), rounding=ROUND_HALF_EVEN)
height1=height.quantize(Decimal('0'), rounding=ROUND_HALF_EVEN)
volume1=(length1*width1*height1).quantize(Decimal('0'), rounding=ROUND_HALF_EVEN)
# 体積 #四捨五入
length2=length.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
width2=width.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
height2=height.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
volume2=(length2*width2*height2).quantize(Decimal('0'), rounding=ROUND_HALF_UP)
# 体積 #切り捨て
length3=length.quantize(Decimal('0'), rounding=ROUND_HALF_DOWN)
width3=width.quantize(Decimal('0'), rounding=ROUND_HALF_DOWN)
height3=height.quantize(Decimal('0'), rounding=ROUND_HALF_DOWN)
volume3=(length3*width3*height3).quantize(Decimal('0'), rounding=ROUND_HALF_DOWN)
# 体積 #切り上げ
length4=length.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
width4=width.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
height4=height.quantize(Decimal('0'), rounding=ROUND_HALF_UP)
volume4=(length4*width4*height4).quantize(Decimal('0'), rounding=ROUND_HALF_UP)
print('誤差なし 体積 {}'.format(volume))
print('偶数丸め 体積 {} 誤差 {}%'.format(volume1, str(((volume-volume1)/volume*Decimal(100)).quantize(Decimal('0.01')))))
print('四捨五入 体積 {} 誤差 {}%'.format(volume2, str(((volume-volume2)/volume*Decimal(100)).quantize(Decimal('0.01')))))
print('切り捨て 体積 {} 誤差 {}%'.format(volume3, str(((volume-volume3)/volume*Decimal(100)).quantize(Decimal('0.01')))))
print('切り上げ 体積 {} 誤差 {}%'.format(volume4, str(((volume-volume4)/volume*Decimal(100)).quantize(Decimal('0.01')))))
実行結果は以下の通りとなります。
誤差なし 体積 1573.425 偶数丸め 体積 1540 誤差 2.12% 四捨五入 体積 1694 誤差 -7.66% 切り捨て 体積 1430 誤差 9.12% 切り上げ 体積 1694 誤差 -7.66%
偶数丸めが一番良い精度になっているのがわかります。このような理由から偶数丸めは銀行や工業製品などで使われます。
まとめ
今回はDecimalについてみてみました。Decimalは10進数での事務処理が必要な場合に良く使われます。特にお金周りは1円の誤差も許されません。もちろん丸めの違いにより結果が異なる事は許されません。きちんと決められた丸めにのっとって計算する必要があります。そのためDecimalが使われます。
一方floatは計算速度が必要な科学技術計算(シュミレーション)などで使われます。シュミレーションでは誤差は全体の総量に対するパーセンテージでどの程度であるかが問題となります。処理速度も必要です。そのためfloat型が最適ということになります。
小数がでてくる計算をおこなう場合に、それぞれの特徴を踏まえどちらを使うのが正しいか判断できるようになれれば良いと思います。
Comment on this article
コメントはまだありません。
Send comments