コンパイラ作成(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言語で変換するプログラム作るかな。車輪の再発明は避けて初めからそうした方が良かったかな。まあでもコンパイラの作成自体が車輪の再発明だしなあ。あれこれ考えたってしょうがないことか。