此算法很有名,来自《雷神之锤3》Quake引擎源码q_math.c的552行,传言是3d游戏开发大神约翰卡马克写的,代码出来之后很震惊,不过据考证最早的实现人并不是他,至于是谁第一个实现的至今成迷,源码如下:
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 = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
return y;
}
这篇文章内容不是原创,主要来自网上一些介绍,博主一开始也没看明白这算法,看了一些网上介绍大概了解了,这里分析一下算法原理,同时做个备忘录:
算法有两步是核心,一个就是魔数:0x5f3759df,还有一个就是
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
下面介绍如下:
输入, 目标是计算 ,列出方程就是:
令 则目标就是求解这个方程的根,
这里有牛顿迭代法可以求解,牛顿迭代介绍比较多,这里不再缀述,直接给出公式:
将代入上式并化简,得到:
这其实就是对应代码中的:
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
由此,可见代码中只进行了一次牛顿迭代就默认收敛了,那么为何作者那么确定能快速收敛,这主要取决于迭代初始值的选择,如果初始值与目标值越近,收敛越快
任何一个浮点数,在计算机中存储如下:
s就1位,符号位置, e...e共8位 m...m共23位 取值全部为0,1
如果用2进制来看(下文中默认都是2进制)
令的值为 ,
(浮点数为了包含负指数情形,因此上式有个偏移量)
则上述浮点数的值为:
并且取值范围在之间
如果把这个浮点数看做一个整数(也就是直接强转为整数,下同),并且令
则这个整数取值为:
(公式1)
同时之间存在如下关系:
现在先把这些公式放到一边,我们回到原问题,其实要求一个迭代初始值,这个初始值是这么推导来的:
要计算 两边取对数,得到:
现在用上面的浮点数表示替换,得到
可以看见两边都有形如的形式
如果把 这个函数近似为线性函数: 代入上式,得到:
再用上面的公式,可以简化为:
如果记:
有:
(2)
其中对应的就是 魔数: 0x5f3759df ,
根据上文分析,表达式(2)意味着:
根据公式(1), 如果把输入的浮点数a,看做整数,其取值就是
则根据(2)计算出的就是浮点数 看成整数的值,
再把这个值强转为浮点数,就是浮点数y的值,
(当然在计算过程中我们近似利用了
所以这样的算出的值是有误差的,但是误差比较小,作为牛顿迭代的初始值,只需要一次迭代就收敛了;)
而公式2对应的源码就是(已添加注释):
y = number; //number相当于公式中的a
i = * ( long * ) &y;//a强转为整数,得到I_a
i = 0x5f3759df - ( i >> 1 ); //i>>1就等于I_a/2 ,
y = * ( float * ) &i; //最后再强转为整数
据此,就分析完了,从上面可以看出魔数主要由确定,至于怎么确定的,感兴趣的读者可以深究,但是的确定只与1个确定函数有关,即:,它的取值与算法的输入输出是无关的,也就是说,不管怎么取,只会影响魔数:0x5f3759df;而不会影响代码的结构
因此,对于我们的目的已经达到了,这个算法的原理已经解释清楚了