バイナリファイルから float 型データを読み込む

ちょっと調子に乗ってたらあんまりにもひどい勘違いに数時間潰したので自戒を込めて晒してみる。

問題と答え(C言語

バイナリファイルからfloat型のデータをひとつ読み込むには?
答え:

fread(&float, sizeof(float), (size_t)1, fp);

如何に遠回りをしたか

最初、とあるバイナリファイルからfloat型のデータを読み込もうとして、次の文字を見つけた

00 00 80 3f

バイナリエディタ等で見てみるとfloat型で1.0になっている。
え、これで1.0なの?
と思っていると、これは単精度浮動小数点数をリトルエンディアンで表記しているからこうなる。
だから読み込むにはビッグエンディアンに戻さないといけない(第一の間違い)

	unsinged char c;
	FILE *fp;
	char str[256];
	//(途中略)
	fp = fopen("./hoge.bin", "rb");
	c=fgetc(fp);
	str[3]=c;
	c=fgetc(fp);
	str[2]=c;
	c=fgetc(fp);
	str[1]=c;
	c=fgetc(fp);
	str[0]=c;
	str[4]='\0';
	// リトルエンディアン文字列をとりあえずビッグエンディアンで詰め直す

	sprintf(str, "%2.2x%2.2x%2.2x%2.2x", str[0],str[1],str[2],str[3]);
	// 16進数 2桁ずつ表記の文字列にして詰め直し

トルエンディアンの文字列をビッグエンディアンに戻すことはできた。

3f 80 00 00

今度はこれをfloat型に変換する。
文字列から変換指定子を使って変数に読み込ませればいい(第二の間違い)
しかし、16進数表記float型文字列をfloat型変数に変換して代入する方法が見つからない。

	// str = 3f800000
	sscanf(str, "%x", &int);
	// 16進数をint型として解釈する

10進数のint型にする時はこれでいい。
じゃあ同じように16進数float型として解釈すればいいじゃん。
と、思いきや。

	// str = 3f800000
	sscanf(str, "%f", &float);
	// 16進数をfloat型として解釈したい

これだと最初の一文字 3.0 しか代入されない。

	// str = 3f800000
	sscanf(str, "%a", &float);
	// 16進数をfloat型として解釈したい

これでもおんなじ結果。
ここで詰まった。
数時間詰まった。
(実はなぜか一回だけうまくいって、でも再現できなくて、再現したくてこだわり続けてしまった。たぶん何かの間違いだった)

たったひとつの冴えたやり方

バイナリファイルのリトルエンディアンをビッグエンディアンに戻して、16進数float型を10進数float型に戻す、そんな処理の関数を書くか?
っていうか誰も作っていないのか?
ググってもなんで見つからないんだ?
標準で用意されてるのか?
それどこヘッダー? どこヘッダーよそれー?
と、書くか探すか迷いながらググっていると、ふとこんな文を見つけた。

バイナリで書かれているデータをどうして変換する必要があるのでしょう?
変換の必要が無いからバイナリなのでは?

http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200208/02080103.txt

確かに仰る通りごもっとも。

少し検索ワードを変えてググってみた。
そして見つけた。
答え:

fread(&float, sizeof(float), (size_t)1, fp);

結論

すぐに思いつくシンプルで素晴らしい解法は、たいてい既に誰かが用意している。

余談:開発環境

OS: Snow leopard 10.6.8
gcc version: i686-apple-darwin10-gcc-4.2.1
インテルの CPU はリトルエンディアンで処理するので、 Windows のバイナリファイルを読み込む時でも気にする必要ないみたい。
Mac OS X のような PowerPC 系のシステムと互換性のある仕様にしたかったら、こう簡単にはいかないのかもしれない(よく分からない)。
面倒だから切り捨てたくなるね!