コンピュータサイエンスとプログラミング: 第1回 浮動小数点数の2進数表現と加算演算

こんにちは、成田です。
これから何回かにわたってコンピュータサイエンスに関する記事を投稿していきます。

はじめに

浮動小数点数の表現には単精度、倍精度、拡張倍精度が存在する。
単精度形式は32bit, 倍精度形式は64bit, 拡張倍精度形式は80bitを使用してそれぞれの形式で符号bit, 指数bit, 仮数bitを表現できる。
ちなみにC言語においてfloat型は単精度, double型は倍精度となる。
本記事内では単純に桁数が少なく見やすい単精度形式を使って言及する。

IEEE754の浮動小数点ビット配列

IEEE754の標準規格は浮動小数点プロセッサや算術コプロセッサに使用されている。
そのため、どのプログラミング言語も大抵はIEEE754の浮動小数点形式に準拠している。
単精度におけるビット配列は次のようになっている。

HO bit(31bit): 符号部
23bit ~ 30bit: 指数部
0bit ~ 22bit: 仮数部

https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Float_example.svg/590px-Float_example.svg.png

参考: Wikipedia - The memory format of an IEEE 754 floating point value.

浮動小数点数の2進数表現

ここで簡単な浮動小数点数の単精度形式における2進数表現の例を示す。
倍精度に関しても算出方法は同じ原理となる。

例) 2.1という10進数の2進数表現

2.1という10進数値を2進数で表現するとどうなるだろう。
まずは2.1を整数部の2と小数部の0.1に分けて考える。
整数部の2は仮に2bitの2進数で表現するなら 10 となる。

小数部の0.1は、次のように小数部分を2倍していき整数の部分だけ取り出すことで2進数表現が得られる。

0.1 * 2 = 0.2    ---> 0
0.2 * 2 = 0.4    ---> 0
0.4 * 2 = 0.8    ---> 0
0.8 * 2 = 1.6    ---> 1
0.6 * 2 = 1.2    ---> 1
0.2 * 2 = 0.4    ---> 0
0.4 * 2 = 0.8    ---> 0
 
(23bitぶん繰り返す.....)
 
結果: 00011001100110011001100

これで整数部と小数部の2進数表現が得られたので、両者を小数点で繋げて仮に次のように表現する。

10.00011001100110011001100

これを正規化し、整数部の桁を1の位に揃える。
その場合21 bitぶん右シフトして仮に次のように表現する。

1.00001100110011001100110 * 2^1

これで仮数部が得られた。
0以外の数値では整数部分は0にならないためこのbitは省略される。

またこれにより指数部が算出できる。
指数部は単精度形式では8bitで表現して次のようになる。

10000000

指数部の算出にはイクセス表現を使う。
イクセス表現というのは8bitで表現できる範囲内で正負の数まで表す表現方法のこと。
具体的には28を基点に正負が分かれて、00000000が-1280111111が011111111が127になる。
この例で算出された指数は 21 の部分なので単純に基数(バイアス値)となる0111111100000001を加算すると指数部が得られる。

   01111111
+) 00000001 
-----------
   10000000

これで指数部が得られたので仮数部と符号部も加えて2進数を表現することができる。
また上述した通り仮数部の最上位bitの1は省略される。
(可読性をよくするため符号部, 指数部, 仮数部を空白で区切っている)

0 10000000 00001100110011001100110

浮動小数点数の加算演算

浮動小数点数の加減算には丸めによる誤差が生じることが多い。
試しに次の評価式をJavaScriptで実行してみるとそれがわかる。

console.log(2.1 + 4.1);
//=> 6.199999999999999

この仕組みを解説する。

  1. まず 浮動小数点数の2進数表現 の節で説明した手順で 2.1 と 4.1 を2進数で表す.
  2. 次に仮数部で省略された1を戻す.
  3. 指数部が大きい方に桁を合わせる.
  4. そして、仮数部同士を単純に加算する.
  5. ここでもし仮数部で桁上げが発生した場合、指数部で桁合わせを行う.
  6. 戻した整数部の1を再度省略する.

加算はこの手順でビット操作を行う。
これらを順に追っていく。

1. まず 浮動小数点数の2進数表現 の節で説明した手順で 2.1 と 4.1 を2進数で表す.

変換後は次のようになる。

2.1: 0 10000000 00001100110011001100110
4.1: 0 10000001 00000110011001100110011

 

2. 次に仮数部で省略された1を戻す.

仮数部で省略された1を戻すため、仮数部は一時的に24bitとして表す。

2.1: 0 10000000 100001100110011001100110
4.1: 0 10000001 100000110011001100110011

 

3. 指数部が大きい方に桁を合わせる.

例の場合は4.1の方が指数部が大きいことがわかる。
そのため2.1の仮数部を2bitぶん右シフトし、それに応じて指数部も合わせる。

2.1: 0 10000000 100001100110011001100110
  ↓ 2bitシフト
2.1: 0 10000001 010000110011001100110011

 

4. そして、仮数部同士を単純に加算する.

   0 10000001 010000110011001100110011
+) 0 10000001 100000110011001100110011
--------------------------------------
              110001100110011001100110

 

5. ここでもし仮数部で桁上げが発生した場合、指数部で桁合わせを行う.

この加算演算では桁上げが発生しなかったためこの手順は省く。

 

6. 戻した整数部の1を再度省略する.

0 10000001 110001100110011001100110
   ↓ 整数部の1(mbs)を省略
0 10000001 10001100110011001100110

これで2.1+4.1の結果を2進数で表現することができた。
試しにこれを10進数に変換してみる。
以下はJavaScriptで実行した際の挙動となる。

console.log( (2**-1 + 2**-5 + 2**-6 + 2**-9 + 2**-10 + 2**-13 + 2**-14 + 2**-17 + 2**-18 + 2**-21 + 2**-22 + 1) * 2**2 )
//=> 6.199999809265137

以上。