2011年11月16日

浮動小数点のカラクリ2

こんにちはexclamationコンドウです。秋も深まり若干肌寒く感じる季節になってきました。
手洗い手(パー)うがいわーい(嬉しい顔)励行しているにも関わらず、風邪ダッシュ(走り出すさま)ぎみで我ながら抵抗力の無さを嘆いておりますたらーっ(汗)


さて、前回に「次回はプログラムネタします」と言いましたがいいネタが思いつかず
またもや浮動小数点ネタで申し訳ありませんがいってみたいと思います。

前回の 浮動小数点のカラクリ です。
まだ読まれていない方は先にこちらを読むと理解が深まります。


今回は前回にやろうとしていた計算(まずは足し算)についてやろうかと思います。

その前に前回のおさらいとしてまずは10進数から浮動小数点に変換する仕組みをさらっと説明します。
10進数で115を、正規化で表してみたいと思います。

やり方は簡単です。まず115を2進数に変換します。

115(10進数) = 1110011(2進数)

次に仮数部を求めます。仮数部は前回も書きました1〜1.999999…の範囲となりますのでまず
この範囲になるよう小数点の位置を決定させます。1〜1.999999…の範囲に収めるには最上位bitの
右隣に小数点を持ってくればできます。

1110011 → 1.110011

小数点は6つ左に移動したので指数部は26(=64)に決まります。
仮数部 1.110011 は 1のところだけを抽出して10進数化して、1 + 1/2 + 1/4 + 1/32 + 1/64 で 1.796875 です。

指数部と仮数部掛け合せると 26 x 1.796875 = 115

となります。簡単ですね。もし小数点のある実数 68.3542 の場合なら1〜1.9999…の範囲になるまで
2で割り続けます。

68.3542 / 2 = 34.1771
34.1771 / 2 = 17.08855
17.08855 / 2 = 8.544275
8.544275 / 2 = 4.2721375
4.2721375 / 2 = 2.13606875
2.13606875 / 2 = 1.068034375

6回続けたので指数部は26、仮数部は1.068034375となりました。
指数部と仮数部掛け合せると 26 x 1.068034375 = 68.3542
具体的な浮動小数点にするには

 符号部 +なので 0
 指数部 127(バイアス値 *1) + 6 = 133
 仮数部 1+0.068034375 の 0.068034375 を固定小数点化 → 0.068034375 x 223 = 570713.7024 → 小数部四捨五入 → 570714

16進数で表現すると、0x4288B55A
もし、0以上1未満の値なら逆に1〜1.9999…の範囲になるまで2倍ずつ行っていき計算した回数分だけ
指数部のバイアス値127から引けばいい訳です。

これで手動にて浮動小数点化できるようになりました。



次はいよいよ加算計算です。まず計算を行う為には以下のルールで進めていきます。


・計算する値の指数部を合せる(結果は仮数部に反映させる)
・指数部を合わせたら仮数部同士を足す
・もし仮数部のbit数をオーバー(オーバーフロー)したら指数部にそれを反映させる

です。実際に行ってみます。

値は何でもいいのですが、246.912(以下A) + 1975.28(以下B) にします。

Aを浮動小数点化すると、0x4376E979
Bを浮動小数点化すると、0x44F6E8F6
更に2進数化してみます。(分かりやすくする為 符号部、指数部、仮数部で "_" 挟みます)

A = 0x4376E979 = 0_10000110_11101101110100101111001
B = 0x44F6E8F6 = 0_10001001_11101101110100011110110

ここで仮数部で省略されている+1.0を設定する為、仮数部のMSBに1を追加し24bitにします。

A = 0_10000110_111101101110100101111001
B = 0_10001001_111101101110100011110110

先のルールに従い指数部を揃えます。揃える場合はどちらか大きい方に揃えます。
Aの指数部は 10000110(2進数) = 134(10進数)
Bの指数部は 10001001(2進数) = 137(10進数)
137 - 134 = 3の差があるのでAの指数部に3を加えて揃えます。3を加えた事で仮数部もそれに合せて調整する必要があります。指数部が23=8倍になったので仮数部は1/8します。1/8は3bit右シフトになりますので

A = 0_10001001_000111101101110100101111

となります。この時溢れた3bitの内の最上位bitが1なら+1して丸め処理しておきます。
これで指数部が合いました。

A = 0_10001001_000111101101110100101111
B = 0_10001001_111101101110100011110110

次に仮数部同士の足し算です。

A = 000111101101110100101111(2進数) = 2022703(10進数)
B = 111101101110100011110110(2進数) = 16181494(10進数)

2022703 + 16181494 = 18204197 = 1000101011100011000100101

仮数部が24bitをオーバーしていなければそのまま仮数部の下位から詰めていきます。オーバーしている場合は1bit右シフトしてから下位から詰めます。

10.00101011100011000100101 ← この場合は24bitオーバーしているので右に1bitシフトした
00010101110001100010010 を仮数部に詰めます。

また仮数部の24bit目以降が0で無ければその分指数部に加味(+1)します。

0_10001010_00010101110001100010010

これで完成しました。


01000101000010101110001100010010 ≒ 211 x (1+0.085054636) = 2222.191894528

10進数レベルで246.912+1975.28=2222.192ですが10進数→2進数→10進数変換した際の情報落ちが出ている為、若干精度悪いですねたらーっ(汗)

足し算のルール見ると分かりますが、指数部に大きな開きのある値同士で計算すると小さい値の仮数部情報がどんどん無くなってしまう事が想像できますね。即ち、「大きい値」に「小さい値」を足しても正しい精度が
得られないという事です。

例えば、1.5 x 213 と 1.5 x 2-12 の足し算では指数部に25の開きがあるので23+1bitの仮数部は0になってしまいます。

ここが浮動小数点の弱点でもあるところです。

以上の作業をプログラム化したものがこれです。


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

float FAdd(float a, float b)
{
FLOATING_VALUE va,vb,vc;
unsigned int kasuuA,kasuuB,kasuuC;
int shift,maxExp;

va.FloatValue = a;
vb.FloatValue = b;
vc.s.Sign = 0;
kasuuA = va.s.Fraction | (1<<23);
kasuuB = vb.s.Fraction | (1<<23);
if(va.s.Exp > vb.s.Exp){
maxExp = va.s.Exp;
shift = va.s.Exp - vb.s.Exp;
kasuuC = kasuuB >> shift; // 指数部の合せ
if(shift){
kasuuC += (kasuuB >> (shift-1)) & 1; // 情報落ちするbitの四捨五入
}
kasuuB = kasuuC;
}else{
maxExp = vb.s.Exp;
shift = vb.s.Exp - va.s.Exp;
kasuuC = kasuuA >> shift; // 指数部の合せ
if(shift){
kasuuC += (kasuuA >> (shift-1)) & 1; // 情報落ちするbitの四捨五入
}
kasuuA = kasuuC;
}
kasuuA += kasuuB; // 仮数部の加算
vc.s.Fraction = kasuuA;
if(kasuuA >= (1<<24)){ // 桁あふれ
vc.s.Fraction = (kasuuA >> 1);
}
kasuuA>>=24; // オーバーフローした分
vc.s.Exp = maxExp + kasuuA;
return vc.FloatValue;
}
注意) 処理を分かりやすくする為、負の数や0に対する計算及びNaNや∞などは一切考慮しておりません。
これをWindowsマシン上で実行すると分かりますがコプロセッサで計算された答えと比較した時仮数部で
最大1bitの誤差があるようです。

***


どうですか?謎だった浮動小数点が少し理解できた気がしませんかexclamation&question
この手の内容はなかなか知り得にくいところだと思われます。
因みに引き算も同じような要領で計算する事で行えます。(アンダーフロー発生時に指数から引けばいけます)
次回もネタに困りそうなら乗算でもやろうかなと思います。乗算は加算よりも簡単です。
ではまたexclamation×2

*1 バイアス(bias) 下駄履きとも言ったりします。(例 下駄を履かせる)


posted by 管理人 at 14:57 | 研究・開発