浮動小数点の基礎知識

2014-03-10

今回は浮動小数点の基礎知識について説明します。 浮動小数についてきちんと理解している人は意外と少ないのではないでしょうか。

浮動小数点は多くのプログラミングで利用されている実数の表現方式です。 C言語などの言語で使われるdoubleやfloatは浮動小数点型です。 対してintやlongなどは整数型です。

浮動小数点の仕様はIEEE 754: Standard for Binary Floating-Point Arithmeticとして策定されていますが、WikipediaのIEEE 754がわかりやすいと思います。 ざっくり説明すると、なじみのあるdoubleとfloatはそれぞれ倍精度浮動小数点、単精度浮動小数点とよばれ、それぞれ64bitと32bitで表現され、そのbit表現は符号部、指数部、小数部によって構成されます。 倍精度と単精度のそれぞれの部に割り当てられるbit数は以下のようになります。

符号部指数部小数部
倍精度1bit11bit52bit
単精度1bit8bit23bit
単精度の浮動小数点は次のように求めることができます。
\displaystyle value = (-1)^{sign} \sum^{23}_{i=1} \left( 1 + b_i 2^{-i} \right) \times 2^{e-127}

同様に倍精度の場合には次のようになります。

\displaystyle value = (-1)^{sign} \sum^{52}_{i=1} \left( 1 + b_i 2^{-i} \right) \times 2^{e-1023}

では、実際にバイナリ表現から浮動小数点を生成してみましょう。 単精度の1.0は上記の式で

sign=0, \forall b_i=0, e=127

のときです。bit列にすると、

00111111 1000000000 00000000 00000000

です。16進数表現にすると

3f 80 00 00

ですね。これを次のようにリトルエンディアンに注意して、char型の配列に格納し、その配列の先頭アドレスをfloatのポインタとして解釈することで、バイト列をfloatとして解釈します。

#include <stdio.h>

int main()
{
  char a [] = { 0x00, 0x00, 0x80, 0x3f };
  float f = *((float*)a);
  printf ("%f\n", f);
  return 0;
}

そしてコンパイルして実行します。

$ gcc test.c
$ ./a.out
1.000000

実際に1.0が表示されました。 同様に倍精度の1.0の場合についても確認しましょう。

sign=0, \forall b_i=0, e=1023

となるので、bit列は

00111111 11110000 00000000 00000000 00000000 00000000 00000000 00000000

となり、16進数表現では

3f f0 00 00 00 00 00 00

となります。同様にリトルエンディアンに注意してバイト列をdouble型として認識させます。

#include <stdio.h>

int main()
{
  char a [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f };
  double d = *((double*)a);
  printf ("%f\n", d);
  return 0;
}

コンパイルして実行します。

$ gcc test.c
$ ./a.out
1.000000

1.0が表示されることを確認できます。

#include <stdio.h>

int main()
{
  char a [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f };
  double d = *((double*)a);
  printf ("%f\n", d);
  return 0;
}

コンパイルして実行します。

$ gcc test.c
$ ./a.out
inf

infが表示されることを確認できます。

#include <stdio.h>

int main()
{
  char a [] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff };
  double d = *((double*)a);
  printf ("%f\n", d);
  return 0;
}

コンパイルして実行します。

$ gcc test.c
$ ./a.out
-inf

-infが表示されることを確認できます。