Pythonで浮動小数点の計算をしていて、桁あふれの処理をするとき、あふれたbitを丸める必要があります。このときに丸める方法ための関数を作成します。このことにより、本当に細かい話ですが小数点以下のおかしな振る舞いがなぜ起ってしまうのかがわかります。
まず、丸めの考え方からです。小数を2進数に変換するときに、倍精度浮動小数点方式では53bitを仮数部として使いますが、これでは割り切れない場合には、最後のbitについて丸め処理をします。
丸め処理において、仮数部(有効な桁)の最後の桁をULP(Unit in the Last Place)とします。また、その次の丸められるbitをGB(Guard bit)、それ以下のbitのorはRB(Round bit)で1つでも”1″が立てば1となります。実際に計算するときには、GBまで計算し、ここで割り切れればRB=0、割り切れなければRB=1と考えられます。
def round_bin(Bin,rb):
ulp=Bin[-2] #unit in the last place
gb =Bin[-1] #Guard bit
sw_up= False
if gb=='1':
if rb==True: #Round bit
sw_up=True
else:
if ulp=='1':
sw_up=True
result=Bin[:-1]
if sw_up==True:
return bin(eval('0b'+result) +eval('0b'+(len(Bin)-2)*'0'+'1'))[2:]
else:
return result
引数BINでは仮数部+GBをまた、GBまで計算して割り切れない場合は、RB=Trueとして受け取ります。
GBが1のとき、RB=Trueということは1つ繰り上がるので、sw_up=Trueとします。RB=Falseのときは面倒です。この場合、求める小数は切り上がった値と切り捨てたときの値との差が全く等しくなります。そこでどちらか一方に丸めてしまうと全体として数値が偏ってしまいます。そこで、このときにはulpが1のときには切り上げ、ulpが0のときには切り捨てとします。これは、10進数の丸めが最後の桁が偶数になるようにするのが一般的ですが、2進数の場合もこれと同じ考え方で最後の桁が0になるようにしているのではないかと考えられます。
次にGB=0の場合には、GB以下は切り捨てられます。これは当然のことといえます。
プログラムを次にように実行してみます。
print(round_bin('110',True))
print(round_bin('111',True))
print(round_bin('111',False))
print(round_bin('101',False))
print(round_bin('110',False))
print(round_bin('100',False))
11 100 100 10 11 10
とりあえず結果は上手くいっているようです。