- 高速逆平方根とは?
- C言語のコード
- 検証
- アルゴリズムの要点
- [1] 逆平方根の計算を対数・指数の計算に置き換える
- [2] 浮動小数点型の内部表現を利用した対数・指数の近似計算
- [3] ニュートン法による収束で精度アップ
- 感想
高速逆平方根とは?
高速逆平方根(fast inverse square root)とは、平方根の逆数 を高速に計算するアルゴリズムです。平方根の逆数は逆平方根とも呼ばれます。逆平方根はベクトルの正規化などに用いられるので、これを高速に計算できるアルゴリズムには大きなご利益があります。
C言語のコード
高速逆平方根の関数を示します。0x5F3759DF という謎のマジックナンバーが目を引きます。(後述)
float invsqrt(float x) { // y = 1/sqrt(x) = 2 ^ (-1/2 * log2(x)) long X, Y; float y; X = *(long*)&x; Y = 0x5F3759DF - (X >> 1); // Magic number! y = *(float*)&Y; // Newton's method const float threehalfs = 1.5F; float x2 = x * 0.5F; y = y * (threehalfs - (x2 * y * y)); // 1st iteration // y = y * (threehalfs - (x2 * y * y)); // 2nd iteration 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) { // use sqrt() double y = 1 / sqrt(x); // fast inverse square root float fx = (float)x; float fy = invsqrt(fx); printf("%f, %f, %f\n", x, y, fy); // error 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%ほどでした。このように、わずかな計算量にもかかわらずかなり精度の良いアルゴリズムです。
アルゴリズムの要点
[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.2] σの最適値
の最適値として、ここでは誤差の絶対値の最大値が最小となる値を考える。
の範囲での
の最大値の半分の値を
とすればよい。
とすると、
のとき
よって
… (5)
グラフにすると下図のようになる。
[2.3] 整数型での解釈
さて、実数 のfloat型バイナリデータをlong型として解釈した整数を
とする。
指数部 ただし
はバイアス、
仮数部 ただし
とすると、(3) (4) より
よって
… (6)
[3] ニュートン法による収束で精度アップ
[2]の方法で求めた近似値から、さらに誤差を0に収束させるためにニュートン法を用いる。
誤差が0であれば、 より
そこで関数 を定義し、
の解をニュートン法で求める。
まず導関数は
よって、 の近似値
に対し、次の漸化式でより正確な近似値
が得られる。
[2]の方法で求めた近似値を初期値 として、この漸化式の計算を繰り返すことでより正確な近似値が得られます。先に示したC言語のコードでは、この漸化式を1回のみ計算しています。(2回目の計算はコメントアウトしています。)
感想
これ思いついたやつ、頭おかしい。
わずかな行数のコードに込められた数学がすごすぎる。