快速平方根倒数计算

2023/07/25 C algorithm 共 1750 字,约 6 分钟

引子:浮点数

计算机中的浮点数被存放为三个部分,分别放置在一个32位存储的三个段中,如图:

SEM
00000 000023 个 0

并且这三段内存表达的数为:S,E,M.

计算机计算对应的浮点数的方法为:

\[(-1)^S \times {1 + M \over 2^{23}}\times 2^{E-127}\]

我们可以举个例子, 3.14在计算机中存储为:

3-14

其中S=0,E=128,M=4781507.

代入上公式计算:

\[{(-1)}^{0} \times {1 + {4781507 \over 2^{23}}} \times 2^{128-127} = 3.1400001049041748046875\]

更快的平方根倒数计算

计算机图形学中,常常会使用一些数学方法,在可接受的精度范围内,加速一些经常进行的数学运算。比如对三维空间进行二维投影时,这就需要对这两个几何体面上的大量点,进行距离\(\sqrt{a^2 + b^2 + c^2}\)倒数的运算投影到摄影机空间,使得物体在二维平面也可以符合近大远小的透视关系:

\(\alpha = {a \over \sqrt{a^2+b^2+c^2}}(1)\) \(\beta = {b \over \sqrt{a^2+b^2+c^2}}(2)\) \(\zeta = {c \over \sqrt{a^2+b^2+c^2}}(3)\)

在上面的过程中,形如下式的函数将被大量计算:

\[y = {1 \over \sqrt{a^2+b^2+c^2}}(4)\]

而且这些计算都是浮点数计算,每秒成千上万次类似的浮点运算,会导致对这个过程进行优化将获得很大的性能提升。

首先,我们对式1进行化简:

\(a^2+b^2+c^2=x\) \(d = {1 \over \sqrt{x}}\)

两端同时取对数:

\[\log_{2}{d} = - {1 \over 2 } \log_2{x}\]

让我们回顾一下我们的目标,我们现在拥有的是一个浮点表示的\(I_x\)(\(S_x\),\(E_x\),\(M_x\)),我们需要拿到一个浮点表示的\(I_y\).

另,因为知道浮点数的近似表达公式为:

\[(1+m)\times 2^e\]

代入上式:

\[\log_2({(1+m_y)\times 2^{e_y}}) = - {1 \over 2 } \log_2({(1+m_x)\times 2^{e_x}})\]

对\(\log_2({1+m_y})\)进行线性化:

\[\log_2{(1+m_y)} \approx m+\alpha\]

代入原式: \(m_y+\alpha + e_y = - {1 \over 2 } (m_x+\alpha) + e_x\) 因为\(m = {M \over 2^{23}}\),\(e = {E-127}\)

得: \({M_y \over 2^{23}}+\alpha + {E_y-127} = - {1 \over 2 } ({M_x \over 2^{23}}+\alpha) + {E_x-127}\)

再把整个表达式乘以\(2^{23}\) ,得到:

\[M_y+E_y 2^{23} = -{1\over2}(M_x+E_x 2^{23})-{3\over2}(\alpha-127)2^{23}\]

左侧就是浮点表示对应的整形数的值\(I_y\),右侧为浮点表示对应的整形数的值\(I_x\)

最终段简化公式为: \(I_y \approx R - {1\over 2} I_x\)

基于这种算法,早在1999年就在游戏《雷神之锤》被应用,源码函数如下:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // 浮点表示法对的整型值I_x
    i  = 0x5f3759df - ( i >> 1 );               // 获得I_y
    y  = * ( float * ) &i;                      // 获得浮点y
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st Newton iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd Newton iteration, this can be removed

    return y;
}

最后,在自己的PC上实验一下,感受一下知识的力量吧!

quicksqrt

运行一次运算的速度从73个tick,提升到了5tick

文档信息

Search

    Table of Contents