Next: , Previous: , Up: Internals   [Index]


16.3 浮動小数点型の内部構造

GMPの浮動小数点演算は,全てのリムを活用し,丸め処理を簡素にすることで高速な計算を実現することを目的としたものです。

mpf_t型の浮動小数点数は,可変桁数の仮数部と,符号付きの単一マシンワードの指数部を持ち,仮数部は符号と絶対値で構成されています。

最大有効リム              最小有効リム

                            _mp_d
 |---- _mp_exp --->           |
  _____ _____ _____ _____ _____
 |_____|_____|_____|_____|_____|
                   . <------------ 小数点

  <-------- _mp_size --------->

各フィールドは以下の通りです。

_mp_size

現時点で使用しているリム数。これが負の時は保持している値が負であることを表わしています。値がゼロの場合は_mp_size_mp_expもゼロが代入され,_mp_dの値は使用されません(未来のバージョンではゼロの場合,_mp_expの値も未定義になるかもしれません)。

_mp_prec

仮数部のリム単位の精度桁数。計算の種類に寄らず,計算結果の_mp_prec個のリムを生成するためのものです(最大桁を格納するリムは必ず非ゼロにする)。

_mp_d

仮数部の絶対値を格納しているリム配列へのポインタ。mpn関数が処理している通り,「リトルエンディアン」形式になっていますので,_mp_d[0]に最小桁を,_mp_d[ABS(_mp_size)-1]に最大桁を格納しています。

最大桁リムは常に非ゼロとなりますが,それ以外の縛りはないので,最大桁に当たる1bitがこのリム内のどの位置に置かれるかどうかは常に変化します。

_mp_prec+1個のリムは_mp_dが示す位置に置かれており,余分な1リムは便利さのために置かれています(詳細は下記参照)。計算中にメモリの再確保が起こらないよう,精度桁数の変更はmpf_set_prec関数を使わないとできないようになっています。

_mp_exp

リム配列内に置かれる指数部で,小数点の位置を規定しています。この値がゼロの時は,小数点が最大桁の すぐ上にあることを意味し,正の値の時はその値の分,最小桁寄りに位置していることを意味します。従って,例えばこの値が>= 1であれば,上記の図に示したような小数点の位置になります。負数の時は,最大桁リムよりずっと上に小数点が位置していることになります。

指数部はどんな値にもなりうるわけで,必ずしも上記の図のようにリム配列内に小数点が位置するとは限りません。上の方にも下の方も外れた位置に来ることもあり得ます。{_mp_d,_mp_size}データに含まれるもの以外のリムはゼロとして扱われます。

_mp_size_mp_precint型ですが,mp_size_t_mp_expは常にlong型です。こうすることである種の64bitシステム上では32bitsフィールドを抱え込むことになりますが,結果として数バイト分のメモリが節約でき,大きな精度桁数と極めて広範囲の浮動小数点数を扱うことができるようになります。


下記の事項についても留意しておいて下さい。

下位桁のゼロ

下位桁リムである_mp_d[0]はゼロになることがあり,大概無視されるものとして扱われます。計算効率アップのために下位桁ゼロを確認したりするルーチンもありますが,大概はスルーします。

仮数部のサイズ範囲

_mp_sizeはリムの数を表わしますが,その値を格納するためにより少ない桁数で済むのであれば,_mp_precより小さくなることもあります。つまり,低精度の値や桁数の小さい整数を高精度のmpf_tに格納すると,計算効率がアップします。

逆に,_mp_size_mp_precより大きくなることもあり得ます。 _mp_d用に用意された_mp_prec+1個のリム全てを使う値であり,mpf_set_prec_raw関数が_mp_precの値を小さく抑えてしまうと,_mp_sizeの値は変化せず,_mp_precよりいくらでも大きくなり得ます。

丸め処理

丸め処理は必ずリムの境界で行われます。非ゼロの_mp_prec個のリムを使って計算しても,最小精度しか要求しないアプリケーションであれば問題ありません。

単純なゼロ方向への「打ち切り(trunc)」しか行いませんので効率的です。余計なリムも,繰り上げも繰り下げも必要ありませんから。

ビットシフト

指数部はリムの中にありますので,mpf_add関数やmpf_mul関数のような基本演算ではビットシフトは起こりません。互いの指数部が異なっている場合は,小数点をずらす処理を行うだけで済みます。

当然,mpf_mul_2exp関数やmpf_div_2exp関数を使うときにはビットシフトが必要になりますが,それは指数部や仮数部でシフトが必要になった時に限られます。

_mp_prec+1個のリムの使用

_mp_dを格納する余分なリム (_mp_prec+1個目のリムのこと)は,mpfルーチンが桁上がりを必要とする時に使用されます。例えばmpf_add関数が_mp_prec個のリムに対してmpn_add関数を実行する時などです。桁上がりが起きなければその結果はそのまま格納されますが,起きてしまえば余分なリムにも格納され,_mp_sizeの値は_mp_prec+1になります。

_mp_prec+1個のリムが使われる時には,その下位桁リムは拡張された精度分を補てんするためには使われませんので,上位の_mp_prec個のリムだけが使用されます。しかし,その下位桁リムをゼロにしたり,繰り下げたりする必要はありません。後に続く処理では,必要な上位桁分だけが使われ,桁数が同じであれば_mp_prec桁の結果を得ることになります。そうでなければ,元々の値と精度が異なってしまいます。

値をコピーするmpf_set関数等は_mp_prec+1リム全部をコピーします。こうすることで,_mp_size_mp_prec+1であるような値でも完全に正確な値を保持することができます。アプリケーションが要求する精度が_mp_precであればそこまでする必要はないのですが,mpf_set関数は正確な値のコピーを作るべきものであると考えているからです。

アプリケーションの精度

__GMPF_BITS_TO_PRECは精度が必要なアプリケーションを_mp_prec桁にコンバートします。ビットで表わされた値は丸められたリム全体に格納されると,余分なリムが加えられます。というのも_mp_dの最上位リムは非ゼロであり,たとえ1ビット分であっても残る可能性があるからです。

__GMPF_PREC_TO_BITSはリバースコンバージョンを行い,コンバートする前に余分なリムをカットします。mpf_get_prec関数で読み取ると,その影響で,桁数がmp_bits_per_limbの倍数に丸められてしまいます。

非ゼロの余分なリムを付け加えるということは,_mp_dに余分のリムを確保して追加する,ということです。例えば,32ビット長のリムを使う場合,アプリケーションが250ビット要求すると,それは8リムに丸められますので,余分なリムが付加され,_mp_precは9リムになります。従って_mp_dは10リム割り当てられます。mpf_get_prec関数で読み取ると,_mp_precマイナス1リムとなり,これに32をかけて256ビットという値になります。

厳密に言うと,1ビットでも1リム必要になるということは,32ビット長の3リムはせいぜい65ビットしか精度を保持しないということになります。しかし, mpf_t型の目的を鑑みると,64bitで十分と考えられます。