Microsoft BASIC の浮動小数点データ

「BASIC でファイルに落とした浮動小数点の数値データを Unix の C で読まなけ ればならなくなったのですが、どうしたら良いのでしょう。BASIC のプログラムは こんなものです」

  OPEN "DATA" AS #1
  RN=1
  ..
  FOR I=1 TO N
    FIELD #1,4*(I-1) AS A$(1),4 A$(2)
    LSET A$(2)=MKS$(D(I))
  NEXT I
  PUT #1,RN
  ..
なんて言われて、20 年以上前に当時のフォーマットを調べたことがあったのを想 いだしました。

サンプルデータを送ってもらったところ、当時と同じらしく、単精度実数 4 バイ トの内容は、16 進表記で書くと、次のとおりと考えてよいようです。

E1  M1  M2  M3
 |   +---+---+
 |       +-------- mantissa
 +----------------- exponent

「exponent」(指数部)は 0x80 の下駄履き表示で、0x7f なら 2^(-1)、0x81 なら 2^1 になります。つまり、2^(E1 - 0x80) です。0x80 のときは例外で、数値 そのものが 0 であることにします。

「mantissa」(仮数部)は「normalized」(正規化)されていますから、

  .1XXXXXX..  (X = 0 or 1)
という2進数で、最上位の桁は必ず 1 ですから、ここに符号を入れてしまうこと にして、
  sign = (M1 & 0x80) ? -1 : 1
というルールにします。

倍精度実数の場合も同じやり方で、M3 の後ろに M5, M6, M7, M8 が追加されるだ けです。

ここまでわかれば後は簡単ですが、読み取ったバイト列を実数に変換するプログラ ム例を付けておきます。「Intel」の「x86」プロセサは下位バイトが先頭になるこ とに注意してください。先程の図と逆順になります。

double val(s) char *s;
{
  int i;
  long k, x;
  double e, sign, m, pow();

  if (s[3] == 0x00) {
	m = e = 0.0; sign = 1.0;
  }
  else {
	sign = (s[2] & 0x80) ? -1.0 : 1.0;
	x = 0x800000 |
		((s[2] & 0xff) << 16) | ((s[1] & 0xff) << 8) | (s[0] & 0xff);
	k = 0x800000;
	for (w = 0.5, x = 0.0, i = 0; i < 24; k >>= 1, w *= 0.5, i++)
		if (x & k)
			m += w;
	e = pow(2.0, (double)((s[3] & 0xff) - 0x80));
  }
  return sign * m * e;
}