數學 —— 其他 —— 快速求逆平方根

Alex_McAvoy發表於2018-10-29

【概述】

越底層的函式,呼叫越頻繁,那麼最底層的數學運算函式的優化至關重要。

當求逆平方根時,一般做法都是用函式返回 1/sqrt(x),但在 雷神之錘3 中,有一快速求逆平方根的演算法。

【知識儲備】

1.單精度浮點數的儲存

在計算機中,單精度浮點數使用 32 位來儲存,其中,最高位為符號位,後面 8 位為整數位 e_x,代表浮點數的指數,再後面 23 位代表小數部分 m_x,依次表示 2^{-1}2^{-2}、...

因此,如果 x 是一個正浮點數,則有:x=2^{e_x}(1+m_x)

如果想將一浮點數轉為整數形式,則需要做如下運算:I_x=E_xL+M_x=L(e_x+B+m_x)

其中,L 是指數部分唯一需要的次數,M 是小數部分對應的整數版本,B 為 127

2.牛頓迭代法

牛頓迭代法,是求解任意連續函式的根值的一種方法。

對於如上函式,現要求這個函式的根:首先猜一個 x_0,假設它是函式的解,但由於其實際不是,因此需要將這個解迭代,使其逼近真正的解。

在 (x_0,f(x_0)) 處作其切線,求得切線方程:y-f(x_0)=f'(x_0)(x-x_0),並求切線的根,可以發現對於真正的解已經逼近了一步。

推廣到 n,繼續迭代,就足夠逼近真正的解:x_{n+1}=x_n-f(x_n)f'(x_n)

此時,f(x_n),f'(x_n) 可被一個統一的函式來表示:g(x)=f(x)f'(x)

令 ε 為當前解與真正解 r 的距離,則:\varepsilon _i=x_i-r

綜合方程,可得:\varepsilon _i+1=\varepsilon _i-g(x_i)

因此,只要 ε 小於某個特定的值,則可認為此時的 x_i 與方程的解十分接近

【原始碼】

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;
 
    x2 = number * 0.5F;
    y = number;
    i = * ( long * ) &y;// evil floating point bit level hacking
    i = 0x5f3759df - ( i >> 1 );//what the fuck?
    y = * ( float * ) &i;
    y = y * ( threehalfs - ( x2 * y * y ) );//1st iteration (第一次牛頓迭代)
  //y = y * ( threehalfs - ( x2 * y * y ) );//2nd iteration, this can be removed(第二次迭代,可以刪除)
 
    return y;
}

【分析】

如果要求一個浮點數的平方根倒數,一般求法為:y=\frac 1 {\sqrt x}

將其轉化為關於 y 的方程,有:f(y)=\frac 1{y^2}-x=0

轉換為牛頓迭代法的方程,有:y_{n+1}=\frac {y_n(3-xy^{2n})}2

此時,對原方程兩邊同取 2 的對數,有:log_2\:y=-\frac 12 \:log_2(1+m_x)

由於 0\leqslant mx<1,則在這個區間內,可以近似取: log_2(1+m_x)\approx mx+\sigma

根據方差的計算,當 σ=0.0430357 時,整體的偏差是最小的,此時上面的等號兩邊應該相當。

將上述公式整合,最終的 I_x 可以寫成:I_x=Llog_2\:x+L(B-\sigma )

則:I_y\approx \frac 32 \:L(B-\sigma )-\frac 12 I_x

其中:\frac 32 \:L(B-\sigma ) = 0x5f3759df

最終,寫成程式碼就是:

i = 0x5f3759df - ( i >> 1 );

【關於原始碼】

求逆平方根的演算法來自 雷神之錘3(quake3) 的作者卡馬克,該演算法並不複雜,其核心就是用牛頓迭代法來不斷逼近,但卡馬克真正厲害的地方,在於他選擇了一個十分神祕的常數:0x5f3759df 來計算猜測值,於是第一次牛頓迭代算出的值已經非常接近 \frac 1 {\sqrt x} ,這樣僅需兩次牛頓迭代就可達到所需精度。

普渡大學的數學家 Chris Lomont 看了以後覺得有趣,決定要研究卡馬克弄出來的這個猜測值有什麼奧祕。

Lomont 在精心研究之後從理論上也推匯出一個最佳猜測值:0x5f37642f,和卡馬克的數字非常接近。

傳奇並沒有在這裡結束。

Lomont 計算出結果以後非常滿意,於是拿自己計算出的起始值和卡馬克的神祕數字做比較,看哪個數字能夠更快更精確的求得逆平方根,結果是卡馬克贏了... 誰也不知道卡馬克是怎麼找到這個數字的。

最後 Lomont 採用暴力方法一個數字一個數字試過來,終於找到一個比卡馬克數字要好上那麼一丁點的數字,雖然實際上這兩個數字所產生的結果非常近似,這個暴力得出的數字是:0x5f375a86。

Lomont 為此寫下一篇論文,"Fast Inverse Square Root"。

論文下載地址:
http://www.math.purdue.edu/~clomont/Math/Papers/2003/InvSqrt.pdf
http://www.matrix67.com/data/InvSqrt.pdf

相關文章