コンピュータ基礎の基礎~浮動小数点の扱いとIEEE754
- 2010/08/02 20:17
- カテゴリー:備忘録
コンピュータ基礎の基礎,今回は浮動小数点演算です。
一般に,我々がコンピュータを扱うときには,整数で表現が可能な演算が中心です。しかし,コンピュータが計算機である以上,小数点を扱う演算も日常的に起こります。
Z80のように乗除算さえ持たないCPUを搭載した昔のパソコンでも,浮動小数点演算が出来た事を考えると,整数しか扱えないCPUも,プログラム次第で浮動小数点が扱えることになりますが,このように2進数で浮動小数点演算を行う場合には,その扱い方,つまりフォーマットが非常に重要です。
かつては,この各社でバラバラだったフォーマットも,IEEE754という国際標準によって統一されており,データフォーマットの違いによるデータ互換のなさなど,不便な点が解消されてきました。
まず,最初に,2進数で小数点の付いた数をどうやって表現するかです。
思い出して欲しいのは,2進数というのは2になると桁が上がる数でした。一桁増えるごとに2倍になるのが2進数です。これはいいですね。
10進法では,桁が増えるごとに10倍になるわけですが,では小数点から以下の桁はどうだったかというと,小数第一位が1/10,小数第二位が1/100と,桁が小さくなるごとに1/10ごとになっていきます。
2進数でも同じ事で,小数第一位は1/2で0.5,小数第二位は1/4で0.25となっていきます。小数点以下が01なら0.25,10なら0.5,11なら0.75となるわけですね。
では具体的に,小数点の付いた数を2進数で表現してみましょう。14.75を2進数で表現すると,まず小数点から上である14は1110です。次に小数点以下ですが,0.75から0.5を引き算すると0.25ですので,小数第一位と小数第二位の両方のビットが立ちます。
よって,14.75は,2進数では1110.11となります。簡単ですね。
浮動小数点というのは,小数点が動く表現方法です。先程の14.75は,1.475x10の-1乗,というわけですが,このうち1.475を仮数,10を基数,そして-1を指数といいます。これも基本ですね。
問題はここから先です。この1110.11という0と1の列びを,どうやってメモリに格納するのかです。例えば小数点から上の1110の部分を何ビットで格納すればいいでしょうか。8ビット?10ビット?各社でバラバラだと,そのデータに互換性が出てこないのも頷けます。
そこで1985年に制定された標準形式がIEEE754です。もう少し見ていきましょう。IEEE754のフォーマットは,以下のようになっています。
(1)基数は2で,基数はデータに含めない。
基数というのは何の2乗とか3乗かを示す数字でした。IEEE754は2進数を扱うフォーマットですから,基数は2です。わかりきったことなのでいちいちこれをフォーマットには入れません,と言う意味です。
(2)仮数は1以上,2未満に揃える。これを正規化という。
先程の14.75も,浮動小数点で表現すると1.475x10の-1乗となりました。このことで仮数部は10未満になります。IEEE754でも同じ事で,仮数部を1以上未満で扱う事にします。
(3)0の表現は,指数と仮数の全ビットをゼロにする。
仮数部を0にすれば絶対にゼロなのですが,IEEE754では仮数部が0で指数部が最大になっているものを∞の表現に割り当てます。ゆえに全ビットゼロとしなければなりません。
(4)仮数も指数も2進数で表現する。
これもまああたり前の話です。14は1110ですし,0.75は0.11です。
(5)MSBが符号ビットで,0が正,1が負を示す。
当然正の数も負の数も扱いますので,符号ビットを考えておかねばなりません。MSB,つまり最上位のビットに符号の役割を与えます。
(6)単精度は32ビットで,符号1ビット,指数部8ビット,仮数部23ビットである。0から22ビットまでが仮数部,23から30ビットが指数部,31ビットが符号ビットである。
IEEE754には単精度と倍精度の2つがありますが,このうち単精度は指数部を8ビット,仮数部を23ビットとしてあります。
(7)単精度の場合は,指数部は実際の指数に127を足し,仮数部は整数部分の1を省略する。
正規化してあるので,仮数部は必ず1以上2未満になりますから,整数部分の1をわざわざ記述する必要がありません。また,指数部に足される127という数値をバイアス値と言います。バイアスを加えるのは,指数部が負の数を取ることがあるからです。
(8)倍精度は64ビットで,符号1ビット,指数部11ビット,仮数部が52ビットである。0から51ビットが仮数部,52から62ビットが指数部,63ビットが符号ビットである。
(9)倍精度の場合,指数部は実際の指数に1023を足し,仮数部は整数部分の1を省略する。
ルールとしてはこんな感じです。
では,2.5を単精度で表現してみましょう。
まず,2.5を2進数で書きます。2は2進数で10,0.5は2進数で0.1ですから,10.1となります。
これを正規化します。10.1x2^0ですから,これをシフトし,1.01x2^1とします。ここで,仮数部が1.01,指数部が1,そして基数が2となります。
仮数部は整数部分を省略しますから,01となります。なお,基数2も省略します。また符号ビットは正ですので0です。
仮数部は1になりますが,これにバイアス値である0x7fを足し,0x80とします。
これらから,以下のようになります。
0 10000000 01000000000000000000000
それでは次に0.1を倍精度で表現してみましょう。
実はこの0.1をに2進数で正確に表現するのが不可能なのです。
0.1ですから,0.5よりも,0.25よりも,0.125よりも小さいです。この段階で0.000まで確定です。
0.1-2^(-1)=-0.4 0.0
0.1-2^(-2)=-0.15 0.00
0.1-2^(-3)=-0.025 0.000
さらにもうヒトケタ小さくしますと,0.0625です。ここでようやく1になり,0.0001です。しかし余りが0.0375でます。
0.1-2^(-4)=0.0375 0.0001
余りである0.0375を使って以後計算します。
0.0375-2^(-5)=0.00625 0.00011
0.00625-2^(-6)=-0.009375 0.000110
0.00625-2^(-7)=-0.0015625 0.0001100
0.00625-2^(-8)=-0.00234375 0.00011001
0.00234375-2^(-9)=0.000390625 0.000110011
0.000390625-2^(-10)=-0.0005859375 0.0001100110
0.000390625-2^(-11)=-0.00009765625 0.00011001100
0.000390625-2^(-12)=0.000146484375 0.000110011001
というわけで,ちゃんと証明したわけではありませんが,どうやら00110011・・・手な具合に循環していそうです。これでは永遠に割り切れることはないでしょう。つまり,0.1は2進数でちゃんと表現出来ないということになるのです。
それはそれとして,先に進みましょう。0.0001100110011・・・を正規化します。1.100110011・・・x2^-4となります。よって仮数部は1.100110011・・・,指数部は-4です。
指数部は倍精度の時には0x3ffをバイアスとして足します。すると指数部は0x3fbです。そう,指数部が負の数を取ることがあるから,バイアスを足すのでしたね。
よって,64ビットで表現される倍精度フォーマットでは,以下のようになります。
0 01111111011 1001100110011001100110011001100110011001100110011001・・・
一見するとこれいいように思いますが,実は循環する数字がそのままカットされているので,決まった範囲に数値が収まるように,丸めを行います。言ってみれば,3.14159・・・とせず,3.14と小数第2位までで表現するのと同じ事です。
IEEE754では,4つの丸めが定義されています。
・最近接偶数丸め(RN)
最も近くの表現できる値へ丸めます。基本的には0捨1入としますが,ちょうど中間の場合には,偶数になるよう,つまり仮数部の一番低位の数字が0になるようにします。誤差の蓄積もなく,もっともおすすめの方法です。
・0方向丸め(RZ)
常に0に近い方に丸めます。いわゆる切り捨てです。
・+∞への丸め(RP)
正の無限大に近い方に丸めます。
・-∞への丸め(RM)
負の無限大に近い方に丸めます。
さて,先程の数字ですが,標準では最近接偶数丸めを使います。入りきらなかった最初の数字は1ですので,これは0に丸めます。つまり,110011は,1000000に丸めます。よって,0.1は倍精度では,
0 01111111011 1001100110011001100110011001100110011001100110011010
となります。
この丸めによって,実は,10進数で0.0000000000000000055511151231257827021181583404541015625もの誤差を含んでいることになるわけですが,これがデジタルコンピュータの本質的な問題であるという事です。