Next: Raw Output Internals, Previous: Rational Internals, Up: Internals [Index]
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_prec
はint
型ですが,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で十分と考えられます。