高速逆平方根(fast inverse square root)のアルゴリズム解説 (original) (raw)
- 高速逆平方根とは?
- C言語のコード
- 検証
- アルゴリズムの要点
- [1] 逆平方根の計算を対数・指数の計算に置き換える
- [2] 浮動小数点型の内部表現を利用した対数・指数の近似計算
- [3] ニュートン法による収束で精度アップ
- 感想
C言語のコード
高速逆平方根の関数を示します。0x5F3759DF という謎のマジックナンバーが目を引きます。(後述)
float invsqrt(float x) {
long X, Y;
float y;
X = *(long*)&x;
Y = 0x5F3759DF - (X >> 1);
y = *(float*)&Y;
const float threehalfs = 1.5F;
float x2 = x * 0.5F;
y = y * (threehalfs - (x2 * y * y));
return y;
}
検証
1から100までの数について、上記の関数で逆平方根を計算し、標準ライブラリのsqrt関数を用いた計算と比較しました。
【テストコード】
#include <stdio.h> #include <math.h>
int main(void) { double ave_error = 0; double max_error = 0;
for (double x = 1; x <= 100; x += 1)
{
double y = 1 / sqrt(x);
float fx = (float)x;
float fy = invsqrt(fx);
printf("%f, %f, %f\n", x, y, fy);
double error = fabs((fy - y) / y) * 100;
ave_error += error;
if (error > max_error) max_error = error;
}
ave_error /= 100;
printf("ave_error = %f, max_error = %f\n", ave_error, max_error);
}
【結果】
グラフがほとんど重なっています。誤差は最大0.175%、平均0.088%ほどでした。このように、わずかな計算量にもかかわらずかなり精度の良いアルゴリズムです。
[1] 逆平方根の計算を対数・指数の計算に置き換える
対数の性質より
… (1)
よって
… (2)
これにより、逆平方根の計算を対数・指数の計算に置き換えることができます。
[2] 浮動小数点型の内部表現を利用した対数・指数の近似計算
C言語等で用いられる float型 (32ビット浮動小数点数) の内部表現のバイナリデータ形式に着目すると、対数・指数の簡易な近似計算ができます。
float型のバイナリデータは最上位ビットから順に以下にようになっています。
- 符号ビット: 1ビット
- 指数部: 8ビット (ただし、+127のバイアスがある。)
- 仮数部: 23ビット (ただし、[0,1) ではなく [1, 2) の範囲を表す。)
指数部が上位にあることに着目してください。ここで大きなトリックがあります。float型のバイナリデータをlong型(32ビット整数)として解釈すれば、それだけでほぼほぼ の近似計算になります。逆にlong型のバイナリデータをfloat型として解釈すればほぼほぼ の近似計算になります。(ただし、指数部のオフセットを考慮すること。また、long型を小数部23ビットの固定小数点数とみなすこと。)
参照: 2^x と log2(x) の高速な近似計算 - 滴了庵日録
これをもう少し詳しく見ていきます。
[2.1] 対数の近似
実数 を、指数部 と仮数部 ![ m \in 0 , 1) を用いて次のように表す。
… (3)
すると
ここで ![ m \in 0 , 1) なので
ただし は近似を調整するパラメータであり、 で最適となる。(後述)
以上より
… (4)
[2.2] σの最適値
の最適値として、ここでは誤差の絶対値の最大値が最小となる値を考える。
![ m \in 0 , 1) の範囲での の最大値の半分の値を とすればよい。
とすると、
のとき
よって
… (5)
グラフにすると下図のようになる。
[2.3] 整数型での解釈
さて、実数 のfloat型バイナリデータをlong型として解釈した整数を とする。
指数部 ただし はバイアス、
仮数部 ただし とすると、(3) (4) より
よって
… (6)
[2.4] 逆平方根の計算とマジックナンバー0x5F3759DF
とすると、(1) より
ここで、 のfloat型バイナリデータをlong型として解釈した整数を とすると、(6) より
よって、
… (7)
ここで、 = 0x5F3759DF が謎のマジックナンバーの正体である。
ただし、(5) で求めた の値から計算すると 0x5F37BCB6 となり、一般的に知られているマジックナンバー 0x5F3759DF と微妙に食い違います。この理由はよく分かりません。
(7) で求めた をfloat型のバイナリデータとして解釈すれば、 の近似値が求まる。
これはまさに (2) の近似計算である。
[3] ニュートン法による収束で精度アップ
[2]の方法で求めた近似値から、さらに誤差を0に収束させるためにニュートン法を用いる。
誤差が0であれば、 より
そこで関数 を定義し、 の解をニュートン法で求める。
まず導関数は
よって、 の近似値 に対し、次の漸化式でより正確な近似値 が得られる。
[2]の方法で求めた近似値を初期値 として、この漸化式の計算を繰り返すことでより正確な近似値が得られます。先に示したC言語のコードでは、この漸化式を1回のみ計算しています。(2回目の計算はコメントアウトしています。)
感想
これ思いついたやつ、頭おかしい。
わずかな行数のコードに込められた数学がすごすぎる。