Pythonでの小数点の計算の怪
Pytonで小数点の計算をするとおかしなことが起こります。
print(f'{0.1:.60f}')
#0.100000000000000005551115123125782702118158340454101562500000
0.1を単純に60桁まで表示させると、小数点第18位から555111・・・とごくわずかですが誤差が生じてしまいます。これは、浮動小数点がからんでいるためです。
次のプログラムは、1未満の小数点を2進数に変換するプログラムです。
from decimal import Decimal
def bin_f(f):
dec=Decimal(str(f))
Bin=''
nth=0
first=0
while dec:
if dec >= 1:
Bin += '1'
dec = dec -1
if first==0:
first=nth
else:
if nth!=0:
Bin += '0'
else:
Bin +='0.'
dec*=2
nth+=1
if nth-first==55:
break
return Bin
print(bin_f(0.1))
#0.0001100110011001100110011001100110011001100110011001100110
55桁以降も延々と続きます。0.1というのは人間にとっては切りの良い数字ですが、2進法を使うコンピュータにとってみると必ずしもきりが良いとは言い切れないということです。これを正規化すると、1.100110011001100110011001100110011001100110011001100110×2の(-4)乗になります。ところで、最後の4桁は0110となっていますが、倍精度浮動小数点方式の仮数部は53桁ですから、後半の2桁10は丸め処理により切り上げられます。このため、前半の01は10となります。そこで、正規化と丸めを考慮したプログラムは次のとおりになります。
正規化と53桁の意味は、こちらを参照
Pythonの小数型で扱うことができる数の大きさ
from decimal import Decimal
def bin_f2(f):
dec=Decimal(str(f))
Bin=''
nth=0
first=0
resudial=False
while dec:
if dec >= 1:
Bin += '1'
dec = dec -1
if first==0:
first=nth
else:
if first !=0:
Bin += '0'
dec*=2
nth+=1
if nth-first==55:
resudial=True
Bin=round_rn(Bin,resudial)
break
return Bin+'0'*(53-len(Bin)),first
print(bin_f2(0.1))
#('11001100110011001100110011001100110011001100110011010', 4)
丸めて53桁に正規化された2進数の値と、バイアスの4(マイナス)をタプルで返します。前のプログラムより少しだけ大きな数値になります。それでは、この結果を10進数に戻します。
def base_bin2dec(nary,nth):
dec = 0
for i in range(len(nary)):
dec += int(nary[i])*((0.5)**(i+nth))
return f'{dec:.60f}'
base_bin2dec(*bin_f2(0.1))
#0.100000000000000005551115123125782702118158340454101562500000
一番初めの結果と一致しました。Pythonの小数点の計算がすっきりとしないのは、53桁に収めるときの丸めに原因があることがわかりました。
しかし、この丸めの計算が厄介で、1つの文章にまとめるのには結構時間がかかりそうです。