ニュートン法 (Newton-Raphson methode) は関数
f(x)
を、近似値 xn
の周りで1次近似したものであるが、
高次の近似により収束速度を高めることができる。
拡張精度や多倍長精度の計算では乗算の計算時間が支配的であるので、 目的とする桁数の計算結果を得るための乗算回数を少なくするために、 高次の収束を示す漸化式を利用するのが望ましい。 ここでは高次収束の方法を逆数や平方根の計算に適用した例を紹介する。
ニュートン法により、逆数
1/A
を求める漸化式は次式で与えられる:有効桁は、繰り返しごとに2倍になる。xn+1 = 2 xn - A xn2
拡張精度や多倍長精度の計算では乗算の計算時間が支配的であるので、目的とする桁数の計算結果を得るための乗算回数を少なくするために、高次の収束を示す漸化式を利用するのが望ましい。
以下では
1/(1 - h)
の級数展開を使用している。
1/A
の 3 次収束の漸化式
hn
は、xn
の誤差とする。 繰り返しごとに有効桁は約3倍になる。hn = 1 - A xn
xn+1 = xn (1 + hn + hn2)
1/A
の 4 次収束の漸化式hn = 1 - Axn
xn+1 = xn(1 + hn)(1 + hn2)
1/A
の 5 次収束の漸化式hn = 1 - A xn
xn+1 = xn (1 + (1 + hn2) (hn + hn2))
同様の手法が平方根の計算にも適用できる。平方根 √A のニュートン法による漸化式は次式で与えられる:
xn+1 = (xn + A / xn) / 2
しかし、この式には
A/xn
の除算を行わなければならないという不利な点がある。
そこで、1/√A
のニュートン法を考えてみると、より良い漸化式が得られることに気がつく。
1/√A
のニュートン法による漸化式xn+1 = xn (3 - A xn2) / 2
つまり、
3/2, 1/2
は定数であるので、上の式では乗算が必要とされるだけである。
√A
を求めるには、B = 1/√A
を求めてから、√A = AB
により√A
を求める。逆数の計算の場合と同様に高次収束の漸化式を利用するのが良い。
以下では、1/√(1 - h)
の級数展開を使用している。
1/√A
の 3 次収束の漸化式hn は、xn の誤差と同じである。繰り返しごとに有効桁は3倍になる。
hn = 1 - Axn2
xn+1 = xn(1 + hn(4 + 3hn)/8)
1/√A
の 4 次収束の漸化式hn = 1 - A xn2
xn+1 = xn (1 + hn (8 + hn (6 + 5 hn)) / 16)
1/√A
の 5 次収束の漸化式hn = 1 - Axn2
xn+1 = xn(1 + (64hn + hn2(48 + 40hn + 35hn2))/128)
1/√A
の 6 次収束の漸化式hn = 1 - Axn2
xn+1 = xn(1 + hn(1/2 + hn(3/8 + hn(5/16 + hn(35/128 + hn(63/256))))))
√A
の 6 次収束の漸化式
1/√A
の場合と同様に、√(1 - h)
の級数展開を使用している。
hn
の計算に除算が必要という重大なデメリットがあるのに要注意。hn = 1 - A/xn2
xn+1 = xn(1 - hn(1/2 + hn(1/8 + hn(1/16 + hn(5/128 + hn(7/256))))))
1/∛A (= A-1/3
) の 6 次収束の漸化式hn = 1 - A xn3
xn+1 = xn + xn hn (1/3 + hn (2/9 + hn (14/81 + hn (35/243 + hn 91/729))))
1/∜A (= A-1/3
) の 6 次収束の漸化式hn = 1 - A xn4
xn+1 = xn + xn hn (1/4 + hn (5/32 + hn (15/128 + hn (195/2048 + hn 663/8192))))
Listing 1:
6 次収束の平方根の C での実装例
double _sqrt(double x) { double a, d, h; int e; /* Lookup table for scaling factor based on exponent parity */ static const double scf[3] = { M_SQRT2, 1.0, M_SQRT1_2 }; if (x < 0) return NAN; /* Return NaN for negative input */ if (x == 0) return x; /* sqrt(0) = 0 */ /* Range reduction: x = d * 2^e, where d ∈ [0.5, 1) */ d = frexp(x, &e); /* Initial approximation of 1 / sqrt(d) using 3rd-order polynomial */ /* Approximation error |err| < 5e-4 over d ∈ [0.5, 1] */ a = 2.605008 + d * (-3.6382977 + d * (2.9885788 - 0.9557557 * d)); /* Scaling: convert 1/sqrt(d) to 1/sqrt(x) */ a = ldexp(a * scf[1 + e % 2], -e / 2); /* Compute residual h = 1 - x * a^2, then apply correction */ /* 6th-order convergence in final refinement */ h = 1 - x * a * a; a *= (1 + h * (0.5 + h * (0.375 + h * (0.3125 + h * (0.2734375 + h * 0.24609375))))); return x * a; /* Return sqrt(x) = x * (1 / sqrt(x)) */ }