バイナリファイルから float 型データを読み込む
ちょっと調子に乗ってたらあんまりにもひどい勘違いに数時間潰したので自戒を込めて晒してみる。
如何に遠回りをしたか
最初、とあるバイナリファイルから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);
結論
すぐに思いつくシンプルで素晴らしい解法は、たいてい既に誰かが用意している。