2011年10月12日

浮動小数点のカラクリ

つい最近まで半袖ブティックで過ごしておりましたが、10月からは肌寒く感じ
衣替えしました近藤です。ご無沙汰しております。

本日で4回目の書き込みになります。今日はあまりご存知ないかもし
れない浮動小数点ネタを書こうかと思います。

最近のコンピュータでは浮動小数点を計算する為のコ・プロセッサが
標準で搭載されておりますが、組み込み系システムなどではいまだ
未実装だったりします。システムで浮動小数点計算が必要無ければ
それで構わないと思います。
が、3Dゲームなどはこれ無しではろくに開発出来ないほど重要な
ファクターになっております。

現在主に使われている浮動小数点は32bit型のfloat、64bit型のdouble
ですが、シェーダーなどでは16bit型などもあったりします。
どの型もbit幅が異なるだけで本質的には同じです。

内部の構造を大きく分けて3つに分類できます。


  ・符号
  ・指数部
  ・仮数部


【符号】

符号は+と−を表すものでどの型であっても1bit幅で最上位bitに
割り当てられます。0の場合が正の数、1の場合が負の数となっています。

【指数部】

指数部は2のn乗のnの部分に該当します。例えば16を表現する場合、
24で4となります。
32bit型の場合は8bit分、64bit型の場合は11bit分割り当てられています。

【仮数部】

指数部だけで表せる数は大雑把(2の累乗単位)になりますので、
その間を補間する為に用意されています。例えば仮数が8bit幅だった場合、
9bit目が1で残りが0の数値を1.0と見なした細かい数値が固定小数として
格納されています。(8bit幅の場合,0x100=1.0で0x80なら0.5といった感じです)
この場合、数式で表すと


          0≦仮数部<1


になります。最大でも1にはなりません。32bit型の場合は23bit分、
64bit型の場合は52bit分割り当てられています。

それぞれの構造での数値表現は以下のようになっています。


    値=(2指数部) × (1.0+仮数部)


指数部は2倍ずつ変化し、12倍までの範囲は仮数部で補われると
いった感じです。できた値に対して符号=1なら負の数に、符号=0なら
正の数になります。
例えば3の場合は (21)×(1+0.5)=3で指数部は1、仮数部は0.5になります。
5の場合は (22)×(1+0.25)=5となり、指数部は2、仮数部は0.25となります。
0の場合は、、、後ほど説明します。

1より小さい小数数値の場合はどう表現するのでしょうか?例えば0.0125など。
実は指数部に細工があります。指数部は32bit型の場合、8bitの幅があります。
8bitだと0〜255まで表現できます。これをそのまま2の指数にすると
20〜2255
が扱えますが指数からだいたい中央の127を引く事で2-127〜2128まで
表現できるようになる訳です。但し、指数部の0と255は特別な意味が
割り当てられており使える範囲は2-126〜2127となります。
2-126は10進数で表すと1.175494×10-38のだいたい小数点以下38桁
ぐらいまで扱えます。
2127は10進数で表すと1.701412×1038の小数値と同じく
だいたい38桁ぐらいです。
32bit型浮動小数点でもかなり大きい値を取り扱う事ができます。
しかし実際の有効な精度といえば仮数の幅に依存するので223
精度で10進数でいうと7桁ぐらいです。

指数部の0と255について説明します。
指数部0で仮数部も0の場合数値は0という扱いになります。
指数部0で仮数部が0以外の場合は非正規化数、指数部255で仮数部0の場合は
無限大、指数部255で仮数部0以外の場合NaN(Not a Number)という特別な
扱いになっています。
表にすると以下の通りです。

種類    指数部   仮数部
------------------------------
ゼロ      0      0
非正規化数  0      0以外
正規化数  1〜254   任意
無限大    255      0
NaN     255      0以外

お気づきかもしれませんが実はゼロを表現する方法が2種類あります。

 符号0、指数0、仮数0の +0
 符号1、指数0、仮数0の −0

どちらも同じ0です。cなどで

if(a == 0.0f)

という判定を行うとどちらも同じ真になるはずです。

64bitのdouble型の場合もそれぞれの幅が広がっているぐらいでほとんど
同じ仕組みになっています。

浮動小数点の仕組みが分かると色々と面白い事ができます。例えば0〜1
(正確には0.9999…)までのランダムな数値を簡単に作る事ができます。

32bit型の場合で説明します。方法はまず仮数部23bit分をランダムな
数値で埋めます。(rand()を使用する場合は注意が必要です。大抵の
処理系では0〜32767までしか返さない為15bit分しかなく残り8bitも
何か設定しないといけません。)
次に指数部は20になるよう127をセットします。これで
(20)×(1+ランダム)になり、1〜1.99999…の値が生成できる
ようになりました。
あとは出来た数値から1を引けば出来上がりです。Cで書くと以下のような
感じです。


union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:23;
unsigned int Exp:8;
unsigned int Sign:1;
}s;
};

float GetRandom()
{
FLOATING_VALUE v;
v.s.Fraction = rand() | ((rand() & 0xff)<<15);
v.s.Exp = 127;
v.s.Sign = 0;
return v.FloatValue - 1.0f;
}


※ビッグエンディアンのシステムではbit-fieldの上位と下位が逆になる場合があります。
ターゲットがWindowsの場合は変更の必要はありません。

ついでにNaNの検出する関数は以下のような感じです。


bool IsNaN(float a)
{
FLOATING_VALUE b;
b.FloatValue = a;
if(b.s.Exp==255 && b.s.Fraction!=0){
return true;
}
return false;
}


他にも絶対値計算や符号反転など簡単にできちゃいます。グッド(上向き矢印)

今回は実際の四則演算なども説明したかったのですがかなり長くなりそうなので
省略させていただきました。

浮動小数点仕様はIEEE754(IEEEは「あいとりぷるいー」と読む)にて仕様が規格化されています。
興味ある方は参照してみて下さい。次回はプログラムネタを扱いたいと思います。ではexclamation


posted by 管理人 at 16:25 | 研究・開発