C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で実装する高速逆平方根(Fast Inverse Square Root)アルゴリズムの解説


この問題では、整数 x が与えられ、それを32ビット浮動小数点数として扱い、高速逆平方根(Fast Inverse Square Root、すなわち 1/√x)を計算することを目的とします。

逆平方根を求めるこのアルゴリズムは、3Dグラフィックスにおけるベクトルの正規化など、特にコンピュータゲームのプログラミングにおいて絶大な効果を発揮する手法として知られています。名作FPS『Quake III Arena』のソースコードに採用されていたことから、「Quake IIIのアルゴリズム」としても広く知られています。

アルゴリズムの手順

ステップ1:浮動小数点値を、同じビットパターンを持つ整数値として解釈します。

ステップ2:その整数値に対して演算を行い、逆平方根の近似値を求めます。ここでは「マジックナンバー」と呼ばれる定数 0x5f3759df を使用します。

ステップ3:ステップ1と同じ方法を使って、整数値を再び浮動小数点値へと戻します。

ステップ4:ニュートン法(Newton's method)による反復計算を1回行い、近似精度をさらに向上させます。

なぜ高速に動作するのか

IEEE 754形式のfloatは、符号部・指数部・仮数部という構造を持っています。このビット列を整数として読み出すと、元の値の対数に近い値が得られるという性質があります。この性質を利用し、定数 0x5f3759df からビット列の半分を減算するだけで、逆平方根の良好な初期近似値を得ることができるのです。従来のように sqrt() を呼び出してから除算する方法と比べ、はるかに少ない演算量で処理できます。

アルゴリズムの動作を示すプログラム

#include<iostream>
using namespace std;

float calcInvSqRoot( float n ) {

    const float threehalfs = 1.5F;
    float y = n;

    long i = * ( long * ) &y;

    i = 0x5f3759df - ( i >> 1 );
    y = * ( float * ) &i;

    y = y * ( threehalfs - ( (n * 0.5F) * y * y ) );

    return y;
}

int main(){

    int n = 256;
    float invSqRoot = calcInvSqRoot(n);
    cout<<"The inverse square root of the number "<<n<<" is "<<invSqRoot;

    return 0;
}

出力

The inverse square root of the number 256 is 0.0623942

実際に 1/√256 = 0.0625 であることを考えると、わずか数ステップの演算で極めて近い近似値が得られていることがわかります。

注意点

この手法は1990年代後半の限られたハードウェア環境では画期的でしたが、現代のCPUには逆平方根用の最適化された命令(SSEの rsqrtss 命令など)が備わっており、コンパイラの最適化も高度です。そのため、現在の開発では通常、標準ライブラリの sqrt() や std::sqrt() を使用する方が安全かつ高速であり、このアルゴリズムは主に歴史的・教育的な価値を持つものといえます。

  1. C++で二分木が対称(鏡像)かどうかを判定する方法

    二分木が与えられ、それが自分自身の鏡像(対称)になっているかどうかを判定する問題を考えてみましょう。対称二分木とは、木全体が自身の鏡像と一致するような二分木のことです。 具体例 例1 入力: 出力: True 説明: 与えられた二分木は自身の鏡像と一致しているため、出力は True になります。 例2 入力: 出力: False 説明: 与えられた二分木は鏡像になっていないため、対称な木ではありません。 この問題の解き方 対称な二分木とは、自分自身の鏡像となっている木のことです。つまり、木の左側の部分木と右側の部分木が互いに鏡像の関係にあるかどうかを確認すればよいことになります。 具体的

  2. C++で二分木の傾斜(Tilt)を求めるアルゴリズムと実装方法

    二分木の傾斜とは?二分木の根ノードが与えられたとき、すべてのノードの傾斜(tilt)の合計を求めて返すことを考えます。二分木の傾斜とは、木の各ノードについて「左部分木に含まれるノード値の合計」と「右部分木に含まれるノード値の合計」の絶対差を計算して得られる値です。子ノードを持たないノード(葉ノード)については、左右どちらの部分木も存在しないため、その傾斜は0として扱います。具体例入力:出力:15与えられた二分木の各ノードにおける傾斜を求めると、以下のようになります。ノード3の傾斜 = 0(葉ノードのため)ノード5の傾斜 = 0(葉ノードのため)ノード7の傾斜 = 0(葉ノードのため)ノード2の