浮動小数点数の特徴を振り返ってみます。
ビット列
コンピューターの最終単位はビット。0か1です。しかし、0か1だけでは表現の幅がないので、複数の0か1を組み合わせることで数字など表します。ここでは、5ビットの数字を考えてみます。
※どうして5ビットにしたかというと、全部のビットの組み合わせを表示できるというだけで、実用的には全く意味がありません。なお、本記事中、数字の後に2とあるのは、2進数という意味です。
UInt5
5ビットで考えられる基本的な数字と云えば、符号なし整数「UInt5」です。
ビット列 | 数字 | ビット列 | 数字 | ビット列 | 数字 | ビット列 | 数字 |
---|---|---|---|---|---|---|---|
00000 | 0 | 01000 | 8 | 10000 | 16 | 11000 | 24 |
00001 | 1 | 01001 | 9 | 10001 | 17 | 11001 | 25 |
00010 | 2 | 01010 | 10 | 10010 | 18 | 11010 | 26 |
00011 | 3 | 01011 | 11 | 10011 | 19 | 11011 | 27 |
00100 | 4 | 01100 | 12 | 10100 | 20 | 11100 | 28 |
00101 | 5 | 01101 | 13 | 10101 | 21 | 11101 | 29 |
00110 | 6 | 01110 | 14 | 10110 | 22 | 11110 | 30 |
00111 | 7 | 01111 | 15 | 10111 | 23 | 11111 | 31 |
これは、000002を0と定義し、1増やすごとに「最下位ビットが0なら1にする。最下位ビットが1なら0にして、一つ上のビットを確認する。そのビットが0なら1にする。1なら0にしてもう一つ上のビットを・・・」の繰り返しです。最終的に111112になり、さらに1増やすと000002に戻ります。
現在一般的なパソコン用CPUは、このような手順でUInt8, UInt16,...を定義しているかと思います。
Int5
符号なし整数よりもよく使われるのが符号あり整数「Int5」です。
Int5(2の補数表現)
ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 |
---|---|---|---|---|---|---|---|
00000 | 0 | 01000 | 8 | 10000 | -16 | 11000 | - 8 |
00001 | 1 | 01001 | 9 | 10001 | -15 | 11001 | - 7 |
00010 | 2 | 01010 | 10 | 10010 | -14 | 11010 | - 6 |
00011 | 3 | 01011 | 11 | 10011 | -13 | 11011 | - 5 |
00100 | 4 | 01100 | 12 | 10100 | -12 | 11100 | - 4 |
00101 | 5 | 01101 | 13 | 10101 | -11 | 11101 | - 3 |
00110 | 6 | 01110 | 14 | 10110 | -10 | 11110 | - 2 |
00111 | 7 | 01111 | 15 | 10111 | - 9 | 11111 | - 1 |
「2の補数表現」と呼ばれる定義です。000002から011112まではUInt5と同じ、011112と100002の間を除けば1ずつ増えていくところもUInt5と同じです。
「100002は-1610ではなく+1610と定義してもよいのでは?」とも思いますが、-1610と定義しておくと「最上位ビットが1なら負の数である」と統一できるので、比較演算などの時に便利です。
Int5(符号+絶対値表現)
ほかにも
ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 |
---|---|---|---|---|---|---|---|
00000 | 0 | 01000 | 8 | 10000 | 0 | 11000 | - 8 |
00001 | 1 | 01001 | 9 | 10001 | - 1 | 11001 | - 9 |
00010 | 2 | 01010 | 10 | 10010 | - 2 | 11010 | -10 |
00011 | 3 | 01011 | 11 | 10011 | - 3 | 11011 | -11 |
00100 | 4 | 01100 | 12 | 10100 | - 4 | 11100 | -12 |
00101 | 5 | 01101 | 13 | 10101 | - 5 | 11101 | -13 |
00110 | 6 | 01110 | 14 | 10110 | - 6 | 11110 | -14 |
00111 | 7 | 01111 | 15 | 10111 | - 7 | 11111 | -15 |
という定義も考えられます。最上位ビットで符号をあらわし、それ以外のビットで絶対値を表しています。
人が見るとわかりやすいのですが、コンピューターが計算するには少々やっかいなところがあります。
2の補数表現で 510+(-310)=210 をあらわすと、001012 + 111012 = 000102 になります。
符号+絶対値表現で 510+(-310)=210 をあらわすと、 001012 + 100112 = 000102 になります。
ここで、同じビット列のUInt5の足し算は、
001012 + 111012 = 000102 (左辺が2の補数表現の510+(-310)と同じビット列)
001012 + 100112 = 110002 (左辺が符号+絶対値表現の510+(-310)と同じビット列)
となり、2の補数表現の場合とUInt5の場合の右辺が同じになります。つまり、2の補数表現を使えば、UInt5とInt5で同じ加算処理を使えるのでCPU設計が簡単になるかと思います。
Int5(バイアス表現)
ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 |
---|---|---|---|---|---|---|---|
00000 | -15 | 01000 | - 7 | 10000 | 1 | 11000 | 9 |
00001 | -14 | 01001 | - 6 | 10001 | 2 | 11001 | 10 |
00010 | -13 | 01010 | - 5 | 10010 | 3 | 11010 | 11 |
00011 | -12 | 01011 | - 4 | 10011 | 4 | 11011 | 12 |
00100 | -11 | 01100 | - 3 | 10100 | 5 | 11100 | 13 |
00101 | -10 | 01101 | - 2 | 10101 | 6 | 11101 | 14 |
00110 | - 9 | 01110 | - 1 | 10110 | 7 | 11110 | 15 |
00111 | - 8 | 01111 | 0 | 10111 | 8 | 11111 | 16 |
という定義もできます。UInt5から15を引いた(バイアスを加えた)、という定義です。000002から111112まで1ずつ増加するので大小の比較が簡単になりますが、符号+絶対値表現と同様に、UInt5とは別の加算処理を行う必要があります。
Float5
IEEE754 にある binary16を参考に、Float5を定義してみます。
表現したい数字cを、c = ± a × 2b という指数表現にし、Float5の最上位ビットは符号、続く2ビットを指数部b、下位2ビットを仮数部aに割り当てます。
符号は、正を0、負を1で表します。これは、2の補数表現や符号+絶対値表現と同じです。
指数部bは、以下のように割り当てます。
ビット列 | 10進数 |
---|---|
00 | -1 |
01 | 0 |
10 | 1 |
11 | 2 |
これは、Int5の最後に紹介したバイアス表現と同じです。
続いて、仮数部aは、以下のように割り当てます。
ビット列 | 10進数 | 2進数 | 補足 |
---|---|---|---|
00 | 1.00 | 1.00 | 指数部が00の場合は0.00 |
01 | 1.25 | 1.01 | |
10 | 1.50 | 1.10 | |
11 | 1.75 | 1.11 | 指数部が11の場合は無限大 |
10進数表現がわかりづらいので、2進数表現もつけました。10進数の場合、指数表現では(整数部1桁.小数部)×10指数部と表現します。2進数の場合も同様に整数部を1桁で表すのですが、1桁に0か1しかありませんので、表現したい値cが0の場合を除けば、整数部は必ず1になります。そこで、整数部を1に固定し、小数点以下2桁にビット列を割り当てます。これで有効桁数を若干増やしています。
さて、以上を踏まえると、Float5は以下のようになります。
ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 | ビット列 | 10進数 |
---|---|---|---|---|---|---|---|
00000 | +0.00×2-1=0.000 | 01000 | +1.00×21=2.000 | 10000 | -0.00×2-1=-0.000 | 11000 | -1.00×21=-2.000 |
00001 | +1.25×2-1=0.625 | 01001 | +1.25×21=2.500 | 10001 | -1.25×2-1=-0.625 | 11001 | -1.25×21=-2.500 |
00010 | +1.50×2-1=0.750 | 01010 | +1.50×21=3.000 | 10010 | -1.50×2-1=-0.750 | 11010 | -1.50×21=-3.000 |
00011 | +1.75×2-1=0.875 | 01011 | +1.75×21=3.500 | 10011 | -1.75×2-1=-0.875 | 11011 | -1.75×21=-3.500 |
00100 | +1.00×2 0=1.000 | 01100 | +1.00×22=4.000 | 10100 | -1.00×2 0=-1.000 | 11100 | -1.00×22=-4.000 |
00101 | +1.25×2 0=1.250 | 01101 | +1.25×22=5.000 | 10101 | -1.25×2 0=-1.250 | 11101 | -1.25×22=-5.000 |
00110 | +1.50×2 0=1.500 | 01110 | +1.50×22=6.000 | 10110 | -1.50×2 0=-1.500 | 11110 | -1.50×22=-6.000 |
00111 | +1.75×2 0=1.750 | 01111 | +無限大 | 10111 | -1.75×2 0=-1.750 | 11111 | -無限大 |
さて、浮動小数点数を扱うときに気をつけなければならないことの一つに、10進数の小数がそのまま表せないと云うことがあります。見てのとおり、0.110刻みの数値を表すことはできません。意外にも、「仮数部2桁の2進数だから、0.2510, 0.5010, 0.7510は表現できる」と思いきや、0.7510しか表現できません。
そして、もう一つ気をつけないといけないのが、数値の間隔(分解能)です。000012から000112までは各値の間隔が0.2510です。しかし、011002から011102までは間隔が1.0010になります。このように、指数部が大きくなるごとに数値の間隔が広くなってきます(ただし、000002から000012の間だけ広くなっています)。
#まとめ
浮動小数点数といえば、「10進数の小数が表せないことがある」点が取り上げられがちですが、「0から離れるほど数値の間隔が広くなる(分解能が落ちる)」ということも気をつける必要がありそうです。