3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Fast Inverse Square Rootの一般化を考えてみた

Last updated at Posted at 2023-06-28

Fast Inverse Square Rootという、Quakeという昔のゲームで使われた超絶すごいアルゴリズムがありまして、計算コストが割りと高い$y = 1/\sqrt x$を超高速で計算できます。

上のビデオで詳しく解説されていますが、この手法は浮動小数点数の仕様をハックしきっていて最高なので、その一般化版を考えてみました。つまり、

$y = x^{-1/2}, x^{-1/4}, x^{-1/8}...$ に対する解です。

これ全部$1/2^n$の形なので$log_2$したときにbit shiftの形に落とし込めるのがミソです。

ビデオで紹介されている箇所はなるべく端折りますが、以下でやっていきます。

(符号少数点数の符号部は全て正とします。)

$\Gamma = y^{-1/2^n} $
$\Leftrightarrow log_2{\Gamma} = {-1/2^n} log_2{y}$
$\Leftrightarrow log_2{(1+\frac{M_{\Gamma}}{2^{23}})2^{E_{\Gamma}-127}} = {-1/2^n} log_2{(1+\frac{M_{y}}{2^{23}})2^{E_{y}-127}}$
$\Leftrightarrow 1/2^{23} (M_{\Gamma} + 2^{23} E_{\Gamma}) + \mu - 127 =
{-1/2^n} (1/2^{23} (M_{y} + 2^{23} E_{y}) + \mu - 127)$
$\Leftrightarrow M_{\Gamma} + 2^{23} E_{\Gamma} = (2^{23} + 2^{23-n})(127-\mu) -1/2^n (M_{y} + 2^{23} E_{y})$

右辺第一項は定数ですが、適当にn=5,そして動画でも出ていた$\mu=0.043$で計算した場合、
(2^23+2^18)(127-0.043) = 1098273521.66 ≒ 1098273522 = 0x417652F2
となります。
最後の数式右辺第二項の$-1/2^n$が、-してn回bit shiftに当たります。

次にNewton法のパートですが、

$y = \frac{1}{x^{1/2^n}}$

を変形して

$y^{-2^n} - x \equiv f(y)$

とします。

導関数は、

$f'(y) = -2^n y^{-2^n-1}$

です。

その為、値更新のための漸化式は、

$y_{n+1} = y_n - \frac{f(y)}{f'(y)}$
$= y_n - \frac{y_n^{-2^n} - x}{-2^n y_n^{-2^n-1}}$
$= y_n + (1 + 1/2^n) y_n - 1/2^n x y_n^{2^n + 1}$

です。

なので、全体のコードとしては以下です(n=5の場合)。

float calcPower(float x, int power){
    tmp = x // puts x^(2^n) of x;
    ans = 1;
    while (power > 0){
        if (power % 2 == 1){
            ans *= tmp;
        }
        tmp *= tmp;
        power >>= 1;
    }
    return ans;
}

float Q_rsqrt( float number )
{
 const int n = 5;
 long i;
 float x2, y, y_pow;
 const float newton1 = 1.03125; // (1 + 1/2^n)
 const float newton2 = 0.03125; // (1/2^n)
 const float newton3 = 33;      // (2^n)

 x2 = number * 0.5F;
 y  = number;
 i  = * ( long * ) &y;                       // evil floating point bit level hacking
 i  = 0x417652F2 - ( i >> n );               // what the fuck?
 y  = * ( float * ) &i;
 y_pow = calcPower(y, newton3)
 y  = y * ( newton1 - newton2 * x * y_pow  );   // 1st iteration
 y_pow = calcPower(y, newton3) // 2nd iteration, this can be removed
 y  = y * ( newton1 - newton2 * x * y_pow  );   // 2nd iteration, this can be removed

 return y;
}

工夫として、累乗の部分は高速化を加えてみました。a^5を計算するとき、aaaaaだと乗算5回ですが、a^2を計算、a^2a^2を計算、a^4aを計算だと乗算3回で済むことを利用しています。

0x417652F2のマジックナンバーや、newwton1, 2, 3の変数は各自でnに合わせて計算していただければと思います。また、これ申し訳ないのですが手元にCの環境がないため検証できません。どなたか検証していただければ嬉しいです。

3
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?