コンパイラ作成(76) 浮動小数点数
今回はお勉強
char型のサポートが粗方終わったんで、次に何やるかって考えたよ。最初はshort型にしようかと思ったんだけど、やることはchar型と大体同じで詰まらないから、double型に手を出してみるよ。今まで浮動小数点数を使ったプログラミングはあんまりしてこなかったし、特にアセンブリレベルではほとんど知識はないよ。ってことでお勉強するよ。
IEEE754
まずはIEEE 754 - Wikipediaから始めるよ。単精度ってのと倍精度ってのがC言語のfloat型とdouble型だよね。単精度が32bit、倍精度が64bitだね。なるほどなるほど。この内、決められたbitを指数を表すのに使うんだよね。これは何となく知ってたよ。単精度の場合、指数部が8bit、仮数部が23bitか。それと正負を表す符号用の1bit。全部合わせて32bitか。ふむふむ。良く読んでいくとけち表現ってのが出てくるね。仮数部の先頭が必ず1になるんで、それを省いて1bit儲けるってことか。これは知らなかったよ。
C言語にはもう一つlong double型ってのもあるよね。これはなんじゃらほいって見て行ったら、拡張倍精度浮動小数点数 - Wikipediaに詳しく乗ってるね。ようはx87の内部形式ってことで80bitなのか。調べてみたらコンパイラでのサポートもまちまちみたいだ。gccやclangはサポートしてるんだね。それもターゲットがx86の場合だけ見たい。他のCPUだと扱えないだろうしね。long double - Wikipediaにも目を通しといたよ。
mycでのサポート
浮動小数点数をサポートするのに何が必要か整理してみたよ。
- Lexerクラスでのサポート
- IEEE754内部形式への変換
- 浮動小数点数の計算方法
最初のLexerクラスの件はやればできるかな。二番目の件は難しそう。三番目はx86_64のレジスタ構成、命令セットを一から勉強しないといけないなあ。
IEEE754内部形式への変換
まずはこれを調べてみるよ。さてどうやって変換するかだな。
- Rubyに頼る
- 自力で頑張る
- 他の言語に頼る
一番簡単そうなのはRubyにお願いする方法だけど、Rubyはlong double型をサポートしてないんだよね。自力で頑張るってのは技術的には面白いけど大変そうだなあ。他の言語、例えばC言語に頼るってのもあるよね。C言語ならstrtoldとかで簡単に変換できるよね。ただこれやるにはRubyからC言語のルーチンを呼び出す方法を調べないといけないなあ。
テストプログラム
ちょっと試してみるよ。
#! /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 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 else b_decimal += "0" end y = y * 10 / 2 i = i + 1 if i >= mantissa_bits then break end end mantissa = (b_integer.insert(1,".") + b_decimal)[2,mantissa_bits] if a[0] == "0" then exponent = (exponent_bias - 2).to_s(2).rjust(exponent_bits,"0") else exponent = (b_integer.size-1 + exponent_bias - 1).to_s(2).rjust(exponent_bits,"0") end if format == :fp80 then ieee754 = "0"+exponent+"1"+mantissa else ieee754 = "0"+exponent+mantissa end =begin 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(16) puts "ieee754 double = " + [n.to_f].pack('G').bytes.map{|n| "%08b" % n}.join.to_i(2).to_s(16)
自力で変換するのとRubyに頼るのをちょろちょろっと書いてみたよ。あってるのかどうか良く分からないけど動かしてみる。
~/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$
お、できたかな。自力での計算とRubyでの計算結果が一致した。もういっちょやってみるよ。
~/ruby$ ./decimal.rb 0.13 自力で変換 ieee754 float = 3e90a3d7 ieee754 double = 3fd2147ae147ae14 ieee754 quad = 3ffd2147ae147ae147ae147ae147ae14 x87 fp80 = 3ffd90a3d70a3d70a3d7 Rubyで変換 ieee754 float = 3e051eb8 ieee754 double = 3fc0a3d70a3d70a4 ~/ruby$
ありゃ駄目じゃん。やっぱりちょろちょろってやったんじゃ無理だったか。あえなく挫折。どっかの計算がバグってるんだろうけど、今日はもう頭が働かないから諦めよう。バグ取りできなかったらRubyからC言語を呼び出す方法を調べよう。それも面倒くさくなったらlong double型のことは一旦諦めよう。