4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

fast inverse square root (高速逆平方根) のようなトリックまとめ

Posted at

この記事について

低レイヤや古いCPU環境では「重い演算をどう軽くするか」が常に課題でした。
ここではQuake III に実装されたことで有名な inverse square root を皮切りに、いくつかのトリックを整理します。

※あくまで技術的な観点での紹介であり、現在ではそれぞれ高速なCPU命令が実装されているため、直接利用することは、ほとんどないと思われることに注意してください。

トリック集

1. Fast Inverse Square Root(逆二乗根)

概要

$1/sqrt(x)$を高速に求めるための手法です。浮動小数点のビット列を符号なし整数として再解釈し、ビット操作で対数的な近似を作り、ニュートン法で精度を改善しています。

効果

  • 特定のCPU/GPU環境でハードウェア演算より高速に逆二乗根を計算

疑似コード

float fast_rsqrt(float x) {
    float x2 = x * 0.5f;
    int i = *(int*)&x;                // float を int として読む
    i = 0x5f3759df - (i >> 1);        // マジックナンバー
    float y = *(float*)&i;            // 変形したiの値を再びfloatとして読んで近似値を得る
    y = y * (1.5f - x2 * y * y);      // ニュートン法で補正(1回)
    return y;
}

2. Fast Square Root(平方根)

概要

平方根 $sqrt(x)$ を近似する手法です。同様にfloatのビットパターンを整数に解釈して指数を半分にして、ニュートン法で補正しています。

効果

  • 除算不要で平方根を計算
  • ゲーム機や組み込み環境で利用
  • ただし、実際は高速逆平方根の応用(x * rsqrt(x))の方が利用されている

疑似コード

float fast_sqrt(float x) {
    int i = *(int*)&x;
    i = (i >> 1) + 0x1fc00000;   // マジックナンバー
    float y = *(float*)&i;
    y = 0.5f * (y + x / y);      // ニュートン法による補正
    return y;
}

3. Fast Reciprocal(逆数)

概要

$1/x$の高速近似の手法です。同様にfloatのビットパターンから初期近似を作り、ニュートン法で補正しています。

効果

  • 除算命令が重い環境で、逆数なしで逆数を計算
  • グラフィックスや物理シミュレーションで利用

疑似コード

float fast_rcp(float x) {
    int i = *(int*)&x;
    i = 0x7EEEEBB3 - i;          // マジックナンバー
    float y = *(float*)&i;
    y = y * (2.0f - x * y);      // ニュートン法で補正(1回)
    return y;
}

4. Fast Logarithm(対数)

概要

$log(x)$の近似です。すべてのテクニックにおいて、この考え方が使われています。

効果

  • 一部のニューラルネットやDSPの高速化に利用
  • exp の逆変換にも応用可能
  • 低精度で十分な場面に限る

疑似コード

float fast_log2(float x) {
    int i = *(int*)&x;
    float y = (float)i;
    return (y * (1.0f / (1 << 23))) - 127.0f; // 粗い近似式
}

5. Fast Exponential(指数)

概要

$exp(x)$を浮動小数点のビットから直接構築して近似した手法です。log の逆手法となっています。

効果

  • シェーダや機械学習の高速化
  • 低精度で十分なケース(エフェクトなど)に有効
  • 低精度で十分な場面に限る

疑似コード

float fast_exp(float x) {
    float y = x * 12102203.0f + 127.0f * (1 << 23); // x をlog2スケールに変換しfloatのビット列を構築
    int i = (int)y;
    return *(float*)&i;                             // exp(x) の近似値
}

補足

なぜ「float を整数として読む」と log₂ 近似になるのか?

先に概要

最終的に欲しいのは「$y = 1/\sqrt{x}$ の数値」です。ただし、この演算が負荷が高い場合において、別の低負荷の演算の組み合わせで近似したい動機があります。

  • 値 $y$ があれば、その IEEE754 floatのビット表現から整数 $I_y$ を作れます
  • 逆に「整数 $I_y$ を float と解釈」すれば、その値 $y$ が得られます
  • この関係は$$I_y = 2^{23}(127 + \log_2(y))$$ となります

逆二乗根の近似をするときに、いきなり負荷が高い $y$ を演算するのではなく

  • 「欲しい $\log_2(y)$」を整数近似で作る
  • そこから「対応するビット列 $I_y$」を作って、floatに戻す

という流れを取ります。この処理が軽くなるには、$\log_2(y)$の整数近似が簡単である必要があります。幸いにも、この処理を近似する手法があり、今回のテクニックに使われています。

float と log_2(x) の関係

IEEE 754 単精度浮動小数点は以下の形で表されます。※( )内の数値はビット数。

[ 符号sign (1) ][ 指数部E (8) ][ 仮数部M (23) ]

数値としては以下の計算式で表されます。

$$
x = (-1)^{sign} \times 2^{E-127} \times \left(1 + \frac{M}{2^{23}}\right)
$$

ここで $E$ は指数部、$M$ は仮数部、127 はfloatのバイアスです。

この式の両辺に対してlog₂ を取ると下記が得られます。

$$
\log_2(x) = (E - 127) + \log_2\left(1+\frac{M}{2^{23}}\right)
$$

ここで $(E-127)$ は「$\log_2$ の整数部」、$M/2^{23}$ は「$\log_2$ 小数部の補正」に対応しています。

一方で、float をそのまま int として解釈すると、そのビット列$I$の値は下記となります。

$$
I = 2^{23} \cdot (E + M/2^{23})
$$

ここで両辺を $2^{23}$ で割り、さらに両辺に-127をつけてみます。

$$
\frac{I}{2^{23}} -127 = (E - 127) + \frac{M}{2^{23}}
$$

この$\frac{I}{2^{23}}-127$ の右辺と $\log_2(x)$ を見比べると、「整数部は同じ$(E-127)$で、小数部だけを $M/2^{23}$ で置き換えた近似」になっていることがわかります。

そのため、下記のように大胆に近似することができると考えられます。

$$
\log_2(x) \approx\left(\frac{I}{2^{23}} - 127\right)
$$

logの値が手に入ったため、ここからfloatに戻すことができます。

$1/\sqrt{x}$の場合は、下記となります。$I$は$x$のfloat表現です。

$$
\log_2(x^{1/2}) = -\frac{1}{2}\left(\frac{I}{2^{23}} - 127\right)
$$

$逆数の場合は、下記となります。

$$
\log_2(x^{-1}) = -\left(\frac{I}{2^{23}} - 127\right)
$$

マジックナンバーの演算 (高速逆平方根のケース)

ビット表現は下記となります。

$$
I_y = 2^{23}(127 + \log_2(x^{1/2})) = 2^{23}\left(127 -\frac{1}{2}\left(\frac{I}{2^{23}} - 127 \right) \right)
$$

$$
= 2^{23}\cdot 127 - \tfrac{I}{2} + 2^{23}\cdot \tfrac{127}{2}
$$
$$
= 2^{23}\Bigl(127+\tfrac{127}{2}\Bigr) - \tfrac{I}{2}
$$

すなわち下記のような形となります。

$$
I_y \approx\ A_0 - (I \gg 1),
\qquad
A_0 = 2^{23}\cdot 190.5 \approx 0x5f400000
$$

これで 「マジックナンバー定数 − (I >> 1)」 という 整数演算の形に到達します。

トリックの整理

改めて整理します。この性質を応用することで

  • 整数ビット列を簡単な定数演算で変形する → log の近似計算が一瞬でできる
  • 「平方根」「逆数」「指数」「対数」など log に関係する関数の初期近似が作れる
  • 仕上げにニュートン法で補正すれば十分な精度になる

という流れになります。
つまり、「浮動小数点 = log 構造を持つ表現形式」 であることを利用しているのが、これらトリックの核心アイデアです。

参考

4
1
0

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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?