3bit符号付き整数を読み取る

TeXの出力するDVIファイルのパーサを書いていたら
3bit signed integer (Big Endian)
という見るからにめんどくさそうなものを読み取るハメになったのでメモ。

Rubyでバイナリを扱う基本

RubyにはArray#packとString#unpackがあり、数値の配列とASCII-8bit文字列(バイナリ的に扱える物)の相互変換ができます。
http://www.ruby-lang.org/ja/man/html/pack_A5C6A5F3A5D7A5ECA1BCA5C8CAB8BBFACEF3.html
String使うとかキモイ、Byte列クラスが欲しい。

自分で書く

基本的にはこれで必要十分なんですがpackテンプレート文字列に「big endian signed 24bit」なんて都合のいいものは無いので自分で書くしかない。

以下1〜4byteのsigned or unsigned, big endianの数値読み取りコード
(ゴリ押し)

def readInt(data, byte, signed = false)
  data = data.unpack("C*")  #まず8bit区切りunsigned数値配列として読み取り
  num = case byte
  when 1
    data[0]
  when 2
    (data[0] << 8) + data[1]
  when 3
    (data[0] << 16) + (data[1] << 8) + data[2]
  when 4
    (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]
  end
  printf("%b\n", num)

  msb = num >> (byte * 8 - 1)
  if signed and msb == 1
    num = -((~num + 1) & [0xff, 0xffff, 0xffffff, 0xffffffff][byte - 1])
  end
  
  num
end


fp = open('hoge.dvi', 'rb') # rb オプション必須
byte = 3
signed = true
data = fp.read(byte)
num = readInt(data, byte, signed)

unsignedならばシフトして足していくだけなので簡単。


signedではさらに

  • MSBを求める
  • MSBが1のとき
  • 値を反転
  • 1を足す
  • 本来のbit数より上位のbitの1をマスクして消す
  • -を付ける

という処理が必要になる。



本当は1〜n byteに対応したほうがカッコいいんだけどDVIでは仕様で4byteまでしか出てこないので問題ない。