若葉塾で使用するC/C++コンパイラおよびJavaで定義する実数型、doubleとfloatはそれぞれ、IEEE規格のreal*8とreal*4である。
C言語コース教材ではフォーマットを紹介しただけだが、これだけでは具体的にどのようなビット列が格納されるのかはわからない。
そこで、上記各フィールドの詳細説明をここに記述しておく。ちなみに、通常のプログラミングでは、ここまで意識する必要はなく、 double、floatのそれぞれの有効桁数と、格納域のサイズを知っていれば十分である。 しかし、他のプラットフォームや言語処理系とのデータの受け渡し時には、内部表現を知っておく必要がでてくる。
それから、いうまでもなく「必要があろうとなかろうと、気になっちゃうよ〜」派の人のためのページでもある。
まず、次のコードをコピーしてfloat.cとしてコンパイルしてみよう。
#include <stdio.h> void dump (unsigned char *p, int n); void main() { int i; union { double d; float f; unsigned char c[8]; } uni; printf("数値:"); scanf("%lf", &uni.d); printf("d = %lf\n", uni.d); dump(uni.c, 8); uni.f = uni.d; printf("f = %f\n", uni.f); dump(uni.c, 4); } void dump(unsigned char *p, int n) { int i; for (i=n-1;i>=0;i--) printf("%02x ", p[i]); printf("\n"); }
これは、任意の数値の内部表現をdoubleとfloatそれぞれについて16進表示するプログラムである。
まず、2を入力してみよう。
C:\src>float 数値:2 d = 2.000000 40 00 00 00 00 00 00 00 f = 2.000000 40 00 00 00
こんなふうに出力されたはずだ。2という数値は、
double型では 40 00 00 00 00 00 00 00
float型では 40 00 00 00
になるという意味である(16進数で)。
さて、これをフォーマットに照らし合わせてみると、
型 | double | float |
---|---|---|
符号ビット | 0 | 0 |
指数部 | 1024 | 128 |
仮数部 | 0 | 0 |
となり、結果としてゼロのように見える。2のはずなのになぜゼロか!? 実はこれにはしかけがあって、ふたつの秘密が隠されているのである。
仮数部の秘密
仮数部は1.XXXXXのXXXXXの部分を表している。上記の場合、仮数部ゼロということは、1.0000…なのである(2進数の小数で)。
指数部の秘密
指数部にはバイアスがかかっていて、ど真ん中がゼロである。つまりfloatの場合127がゼロ、doubleの場合1023がゼロである。
指数部にも符号を持てばよいのにと思う人は多いだろうが、おそらく演算が複雑になるためこうなっている。 それはさておき、上記の例ではどちらも指数部 1 であるから、仮数部の小数点位置を1桁右へシフトする。すると 10.0000…(=2.0)になるというわけだ。
まだ半信半疑だろう諸君のために、次に0.5を入れてみよう。
C:\src>float 数値:0.5 d = 0.500000 3f e0 00 00 00 00 00 00 f = 0.500000 3f 00 00 00
各フィールドは、
型 | double | float |
---|---|---|
符号ビット | 0 | 0 |
指数部 | 1022 | 126 |
仮数部 | 0 | 0 |
となり、こんどは小数点を左へ1桁シフトすることになる。すなわち0.1000…(=0.5)となる。
練習問題
さあ、これであなたは完全に浮動小数点の見方をマスターした。そこで問題である。float.exeを実行して1.5を入力し、 double、floatともに仮数部の最初のビットがどうなるか観察せよ。それは何を意味するか考えよ。 納得できるまでさまざまな値を入力してみること。