ニュートン法を使った2進多桁の整数除算(その2)
プログラムメモ | 2011/02/20 Sun 08:56
| 前回、除算 x = p / q をNewton法を使って解くプログラムを紹介しましたが、
単独の初期値を使った方法では、あまり早くありません。
数万bitを超える多桁演算の場合、64bit精度の初期値を使ったとしても、あまり速度に差が出ません。
そこで大きな桁(数万bitを超える)で計算を開始するのではなく、除数(q)の先頭部分を使った小さな桁で計算を開始して、これをNewton法の初期値とし、もう少し大きな桁でNewton法を解く、
これを繰り返して最終的な答えを得る方法を紹介します。
少しコード修正しました(2011.2.11)
newtondivは、Newton法で (2n / q) を求める関数です。
初回は先頭64bit程度で計算します。
2回目は、初回の計算結果を初期値として、64+(除数のBit数/4)での計算。
3回目は、2回目の計算結果を初期値として、64+(除数のBit数/2)での計算。
4回目は、3回目の計算結果を初期値として、全桁での計算。
と計算桁を増やしていきます。
除数・被除数のとり方にもよりますが、計算時間は明らかに短くなります。
この方法は、Bigintの演算回数としては、効果はないのですが、
可変長のFFT乗算を用いる場合、演算量は2の冪数桁単位に増加するので、
Totalの計算量としては、こちらの方が有利です。
4分割としていますが、利用するBigint(多倍長整数)クラスで最速となるよう調節する必要があります。
これについては、「高橋 大介, 金田 康正. "多倍長平方根の高速計算法". 情報処理学会研究報告 95-HPC-58, pp.51-56.」が詳しいです。
文献に従えば、初期値に倍精度結果を使う・3〜4分割程度・FFT再利用、を行えば最大限の高速化を望めますが、このサンプルコードではそこまで対応できていません。
ここでは説明を簡単にするため初期値に(2^64)を与えています。
より正確な初期値のとり方や、詳細アルゴリズムはこちら
整数除算の詳細解説
除数・非除数の桁差が小さければ、このような方法もあります。
回復法を使った2進多桁の除算
同様の計算方法を行っている、整数平方根解説はこちら
整数平方根での詳細解説
このアルゴリズムを利用した16進・2進・10進電卓です。
整数だけですがおよそ100万桁まで計算できます。
(注)ここでのBigintクラスは、多倍長整数を格納する仮想のクラスであり、実在するものではありません。
Tags: プログラムメモ
単独の初期値を使った方法では、あまり早くありません。
数万bitを超える多桁演算の場合、64bit精度の初期値を使ったとしても、あまり速度に差が出ません。
そこで大きな桁(数万bitを超える)で計算を開始するのではなく、除数(q)の先頭部分を使った小さな桁で計算を開始して、これをNewton法の初期値とし、もう少し大きな桁でNewton法を解く、
これを繰り返して最終的な答えを得る方法を紹介します。
少しコード修正しました(2011.2.11)
Bigint newtondiv(const Bigint& q, const int& n, const Bigint& init){ Bigint x(init), m(0), c2(2); c2 <<= n; while(m != x){ m = x; x *= c2 - q * x; x >>= n; } return x; } Bigint iDiv(Bigint p, Bigint q){ int pb = p.length(); // 被除数のbit数 int qb = q.length(); // 除数のbit数 int n; int b = 64; int step = qb / 4; Bigint d = 1; d <<= b; // Newton法の初期値として, 1 << b を与える。 d = newtondiv(q >> (qb-b), b*2, d); // 1回目 b += step; d <<= step; d = newtondiv(q >> (qb-b), b*2, d); // 2回目 b += step; d <<= step; d = newtondiv(q >> (qb-b), b*2, d); // 3回目 d <<= (pb-b); n = pb + qb; d = newtondiv(q, n, d); // 4回目 d *= p; // d = p / q d >>= n; // 誤差修正 if (p >= (d+1)*q){ d += 1; } return d; }
newtondivは、Newton法で (2n / q) を求める関数です。
初回は先頭64bit程度で計算します。
2回目は、初回の計算結果を初期値として、64+(除数のBit数/4)での計算。
3回目は、2回目の計算結果を初期値として、64+(除数のBit数/2)での計算。
4回目は、3回目の計算結果を初期値として、全桁での計算。
と計算桁を増やしていきます。
除数・被除数のとり方にもよりますが、計算時間は明らかに短くなります。
この方法は、Bigintの演算回数としては、効果はないのですが、
可変長のFFT乗算を用いる場合、演算量は2の冪数桁単位に増加するので、
Totalの計算量としては、こちらの方が有利です。
4分割としていますが、利用するBigint(多倍長整数)クラスで最速となるよう調節する必要があります。
これについては、「高橋 大介, 金田 康正. "多倍長平方根の高速計算法". 情報処理学会研究報告 95-HPC-58, pp.51-56.」が詳しいです。
文献に従えば、初期値に倍精度結果を使う・3〜4分割程度・FFT再利用、を行えば最大限の高速化を望めますが、このサンプルコードではそこまで対応できていません。
ここでは説明を簡単にするため初期値に(2^64)を与えています。
より正確な初期値のとり方や、詳細アルゴリズムはこちら
整数除算の詳細解説
除数・非除数の桁差が小さければ、このような方法もあります。
回復法を使った2進多桁の除算
同様の計算方法を行っている、整数平方根解説はこちら
整数平方根での詳細解説
このアルゴリズムを利用した16進・2進・10進電卓です。
整数だけですがおよそ100万桁まで計算できます。
(注)ここでのBigintクラスは、多倍長整数を格納する仮想のクラスであり、実在するものではありません。
Tags: プログラムメモ
多桁の整数値 P, Q の除算があったとして、 |
Newton法(2次漸化式)を用いて整数除算を行う。 |
ここで P,Q の有効bit数を それぞれ p、q として、とする。 |
式を変形して、を解く。 |
初めに、を解くのは、演算中の有効桁数が p を下回らないようにするためである。 |
の漸化式は次のように表せる。 |
Newton法を使って(n)回の繰り返しにより、整数桁数分の精度が得られたとすれば、D は、 |
となる。具体的なコード例は、newtondiv()を参照のこと。 |
Newton法の計算では、初期値の工夫によりループ回数が削減できる。 |
例えば、Qが10進で300桁以下の小さな整数であれば、多倍長整数を倍精度小数点数(double)に 変換して計算できる。5000桁以下であれば拡張倍精度小数点数で演算できるが、 VCではアセンブラが必要になる。 |
もっと大きな桁の場合、多倍長整数(Q)の上位bitを取り出して浮動小数点演算を行う。 |
(Q) の上位bit部分を (Q') として、次式が成り立つように (m) を取る。 |
ここでは初期値となる近似値を求めたいので、(C) を無視すると |
従って、(Q') を浮動小数点形式で表現可能な範囲に取れば、は簡単に計算できる。 |
ところで浮動小数点で計算すると、は整数部を持たない。 |
整数部で53bit(倍精度),64bit(拡張倍精度)の精度を持つように、適当な大きさの 2の冪数 (2a) |
を掛ける。その後、浮動小数点形式から多倍長整数に戻し、多倍長整数に |
を掛ければ良い。 |
実際には浮動小数点数の仮数部をそのまま多倍長整数に入れ、 |
指数部にわずかな操作(加減算)を行った値で、シフト演算するだけである。 |
但し、処理系に依存したベタベタなコードになるのでサンプルには載せていない。 |
Newton法の初期値算出にNewton法を使う | |
FFT乗算を用いた多桁演算に於いては、初期値を一度だけ与えて、 Newton法を用いるのは効率的ではない。 | |
徐々に計算桁数を増加させて、Newton法を繰り返す方法が一般に用いられる。 | |
ここでは4回に分けてのNewton法を繰り返す場合について考えてみる。 | |
q[bit]の2進数(Q)を考え、 このときを求めるものとする。(b > q) Q に適当な初期値を与え、 その上位a bit部分をQ0としてR0を次式で定める。 分子の指数部を(2a)とするのは整数演算の桁落ちにより 有効桁がa bitを下回らないようにするためである。 これをNewton法で解いた解をR'0 とする。 計算幅を(m bit)広げてQ1について考えると Q1≒Q0 x 2m であるから R1を初期値としてNewton法でR'1を求め、次式に代入する。 R2を初期値としてNewton法でR'2を求める。 | |
q bit 長の計算に対して、残り z bitあるとすれば ところで求めたいのはであり、定義からなので と変形できるが、であるので、 従って、 を初期値として、を求めれば良い。 具体的なコード例は、idiv()を参照。 この方法は、b と q の差が小さい場合に有効であるが、 差が大きな場合、最終段のNewton法が処理時間をほぼ独占してしまう。 下図で考えてみる。 最初のターゲットを として前出の方法で解く。 例えば、初期値 a により、q bit幅で初段のNewton法を行い、 2段目として、 を解く。 最終段として、R x 2k を初期値として、 を解く、等の方法が考えられる。 結局のところ、k をどの位に取るか、Newton法を何段位に分割するか、等は 利用する多倍長クラス(の乗算)が最速の性能を発揮できるよう調整すべきだろう。 1変数しか扱わない平方根と異なり、多倍長・多変数関数に関する最適化手法は簡単ではなさそう。 | |
author : HUNDREDSOFT | - | -