コンパイラ作成(77) 続浮動小数点数
IEEE754内部形式への変換
前回の続きで頑張ってみるよ。
#! /bin/sh exec ruby -S -x "$0" "$@" #! ruby # # ieee754 # def ieee754_binary(n,format=:double) a = n.split('.') integer = a[0] if a.size < 2 then decimal = "" else decimal = a[1] end b_integer = integer.to_i.to_s(2) b_decimal = "" if format == :float then mantissa_bits = 23 exponent_bits = 8 exponent_bias = 127 elsif format == :double then mantissa_bits = 52 exponent_bits = 11 exponent_bias = 1023 elsif format == :quad then mantissa_bits = 112 exponent_bits = 15 exponent_bias = 16383 elsif format == :fp80 then mantissa_bits = 63 exponent_bits = 15 exponent_bias = 16383 end y = 5 i = 0 x = 0 b = 0 b_flag = true if decimal != "0" && decimal != "" then loop do x = x * 10 if i < decimal.size then x += decimal[i].to_i end if x >= y then b_decimal += "1" x = x - y b_flag = false else b_decimal += "0" if b_flag then b += 1 end end y = y * 10 / 2 i = i + 1 if i >= mantissa_bits + b + 2 then break end end else b_decimal = "0" * mantissa_bits end # mantissa = (b_integer.insert(1,".") + b_decimal) _mantissa = (b_integer + b_decimal) # mantissa = mantissa[mantissa.length-mantissa_bits,mantissa_bits] #p _mantissa idx = _mantissa.index("1") #p idx if idx then mantissa = _mantissa[idx+1,mantissa_bits] if _mantissa[idx+1+mantissa_bits] == "1" then mantissa = (mantissa.to_i(2)+1).to_s(2).rjust(mantissa_bits,"0") end else mantissa = "0" * mantissa_bits end if idx then if a[0] == "0" then exponent = (exponent_bias - idx).to_s(2).rjust(exponent_bits,"0") else exponent = (b_integer.size-1 + exponent_bias).to_s(2).rjust(exponent_bits,"0") end else exponent = "0" * exponent_bits end if format == :fp80 then ieee754 = "0"+exponent+"1"+mantissa else ieee754 = "0"+exponent+mantissa end =begin p b p b_integer,b_decimal p mantissa,exponent p ieee754 =end return ieee754 end n = ARGV[0] puts "自力で変換" puts "ieee754 float = " + ieee754_binary(n,:float ).to_i(2).to_s(16) puts "ieee754 double = " + ieee754_binary(n,:double).to_i(2).to_s(16) puts "ieee754 quad = " + ieee754_binary(n,:quad ).to_i(2).to_s(16) puts "x87 fp80 = " + ieee754_binary(n,:fp80 ).to_i(2).to_s(16) =begin fp80 = ieee754_binary(n,:fp80 ) p fp80[0, 16].to_i(2).to_s(16) p fp80[16,64].to_i(2).to_s(16) =end puts puts "Rubyで変換" #puts "ieee754 float = " + [n.to_f].pack('g').bytes.map{|n| "%08b" % n}.join.to_i(2).to_s(2) puts "ieee754 float = " + [n.to_f].pack('g').bytes.map{|n| "%08b" % n}.join.to_i(2).to_s(16) #puts "ieee754 double = " + [n.to_f].pack('G').bytes.map{|n| "%08b" % n}.join.to_i(2).to_s(2) puts "ieee754 double = " + [n.to_f].pack('G').bytes.map{|n| "%08b" % n}.join.to_i(2).to_s(16)
自力での変換のバグを頑張ってなんとか潰した。かなり悪戦苦闘したんで、小汚いプログラムになっちゃったよ。前回のにはいっぱい抜けがあったよ。でも色々やってみたんで、浮動小数点の内部形式への理解は進んだ。変換の時に最後の桁を切り上げるとか、0.0の場合は特別扱いが必要だとかね。
動作テスト
~/ruby$ ./decimal.rb 10.78 自力で変換 ieee754 float = 412c7ae1 ieee754 double = 40258f5c28f5c28f ieee754 quad = 400258f5c28f5c28f5c28f5c28f5c28f x87 fp80 = 4002ac7ae147ae147ae1 Rubyで変換 ieee754 float = 412c7ae1 ieee754 double = 40258f5c28f5c28f ~/ruby$ ./decimal.rb 0.13 自力で変換 ieee754 float = 3e051eb8 ieee754 double = 3fc0a3d70a3d70a4 ieee754 quad = 3ffc0a3d70a3d70a3d70a3d70a3d70a4 x87 fp80 = 3ffc851eb851eb851eb8 Rubyで変換 ieee754 float = 3e051eb8 ieee754 double = 3fc0a3d70a3d70a4 ~/ruby$
昨日、間違ってたやつもちゃんと変換できるようになったよ。他の数値も何個かテストしてみたけど問題は見つからなかった。でも気になるところがあったよ。
~/ruby$ ./decimal.rb 0.0 自力で変換 ieee754 float = 0 ieee754 double = 0 ieee754 quad = 0 x87 fp80 = 8000000000000000 Rubyで変換 ieee754 float = 0 ieee754 double = 0 ~/ruby$
これのfp80の数値合ってるのかな。何となく間違ってる気がするよ。fp80についてはRubyでの変換と突き合わせて検算できないから他の方法をとらないと駄目だな。C言語で変換するプログラム作るかな。車輪の再発明は避けて初めからそうした方が良かったかな。まあでもコンパイラの作成自体が車輪の再発明だしなあ。あれこれ考えたってしょうがないことか。