前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >贪心算法求快速平方根倒数算法中的“魔术数字”【含matlab源代码】

贪心算法求快速平方根倒数算法中的“魔术数字”【含matlab源代码】

作者头像
巴山学长
发布2021-08-26 10:44:08
1.2K0
发布2021-08-26 10:44:08
举报
文章被收录于专栏:巴山学长巴山学长
快速平方根倒数算法(Fast InvSqrt)是一种快速计算平方根的倒数的算法,常用于向量标准化运算,在光照渲染中有重要应用。此算法最早可能是于90年代前期由SGI所发明,后来于1999年在《雷神之锤III竞技场》的源代码中应用。因其中神秘的十六进制“魔术数字”0x5f3759df而出名。本文将使用matlab和c++混合编程,使用贪心算法计算出这个“魔术数字”的值。

一、快速平方根倒数算法简介及实现

1.1 算法简介

在计算平方根的倒数时,传统的计算方法是先计算a的平方根sqrt(a),再计算它的倒数1/sqrt(a)。但在计算平方根时使用了牛顿迭代法,大量的浮点运算速度很慢。

而快速平方根倒数算法则将输入的32位浮点数a作为一个整数i,用“魔术数字”0x5f3759df减去i右移一位的值产生近似的估值y,再使用牛顿迭代法迭代一次,就得到了相当精度的计算结果。

此算法仅通过简单的位操作和整数减法就得到了一个近似的估计值,浮点运算只包含一次牛顿迭代,可以极大的提高运算速度。

其c/c++源代码如下所示:

代码语言:javascript
复制
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;
}

1.2 Matlab实现

此算法巧妙的利用了32位浮点数的编码格式。但通过指针将32位浮点数转化为32位整数的运算(以及其逆运算)很难在matlab中实现,但很容易通过c/c++实现。因此我们使用c++实现了float2int32和int32_2float这两个函数,它们将输入的浮点(整数)向量/矩阵中每一个元素转化为整数(浮点数)。编译为mex文件后它们可以被matlab直接调用。

程序列表

二、使用贪心算法计算“魔术数字”

2.1 计算“魔术数字”的最优化问题

理想的“魔术数字”(记为R)应当能够让计算结果的偏差最小化,也就是如下的非线性整数规划问题:

式中目标函数Cost(R)代表R作为魔术数字时算法的误差。为了较好的衡量计算误差,我们在正实数范围内选取1000个点,记为 as;通过传统的取平方根再求倒数方法计算出的精确值,记为rs。代码如下:

as=single(10.^linspace(-8,8,1000));

rs=1./sqrt(as);

再调用FastInvSqrtFloat函数计算出R作为魔术数字时的近似结果es,用es与rs的平均均方误差作为的值。

2.2 初始值的选取

由于自变量只有一个R,我们使用相对简单的贪心算法求解R的值。贪心算法的初始值最好位于全局最优解的邻域,否则往往会陷入局部最优解。快速平方根倒数速算法的思路表明通过R计算出的估值ei = R - bitshift(ai,-1)是比较好的近似结果,反而言之,用精确结果对应的整数ri代替ei应当可以计算出比较接近的R。因此我们分别将前面提到的as和ri它们转化为32位整数向量ai和ri,再计算出R的初始值。计算流程如下如下:

ai=float2int32(as);

ri=float2int32(rs);

R=int32(mean(ri+bitshift(ai,-1)));

得到R的初始值为1597311331,即0x5F350963,与参考代码中的0x5F3759DF(参考值)较为接近。记迭代初始值。在区间[-10^7+R0,10^7+R0]内均匀选择2001个R值计算对应损失函数值,得到R0邻域内R-Cost(R)的关系:

如上图所示,参考值和初始值非常接近最优解,可以通过简单的贪心算法迭代求得最优解R*。

3、贪心算法计算

我们假设迭代的步长为100,每次迭代时对当前的R值施加范围内的随机扰动:R(n+1)=R(n)+RAND, RAND∈[-100,100]。如果Cost[R(n+1)]<Cost[R(n)],则接受新的R作为当前值,否则放弃新的R,重新施加随机扰动。迭代直到连续10000个新的R值不被接受为止。

经过迭代,一共采纳过1428个新的值,误差由3.0944逐步下降至2.4536。最终得到最优解R* = 1597385922 = 0x5F362CC2。迭代结果如图所示(以更高的精度绘制了最优解邻域内误差函数的图像)。

三、检验

上面的太过抽象?下图为你展示了使用快速平方根倒数法计算4.0的-0.5次方的过程。

随机选取几个数,以上文所得的R* = 1597385922为“魔术数字”,用快速平方根倒数算法计算,结果如下:

可见使用该“魔术数字”的计算精度较高。

四、讨论与总结

4.1 计算出的最优解0x5F362CC2与参考源代码中的0x5F3759DF不同,因为两个数字的来源不同。前者源于贪心算法寻找得到的最优解,后者则来源未知,也许是理论计算所得。

4.2 最优的“魔术数字”的值与待计算的a值的分布有关。我认为对于特定用途(如光照渲染)的快速平方根倒数算法可以统计a值的概率分布(如需要正规化的向量二范数的分布),根据特定的a值分布来改进Cost函数,再通过最优化方法计算出特定用途下误差最小的“魔术数字”。

4.3 双精度浮点数同样可以采用该算法,只需将代码中的单精度浮点数换为双精度浮点数,32位整数换为64位整数即可。

本文涉及到的完整程序已上传至matlab编程爱好者Q群,如有需要的伙伴请在公众号中回复“QQ”加群领取,在群文件matlab爱好者公众号数据及程序文件夹下的《快速平方根倒数算法》。。

感谢TokiNobug给公众号投稿,欢迎更多爱好、喜欢matlab编程的朋友来稿,在公众号回复“投稿”了解投稿详情。

参考资料:

[1] https://www.zhihu.com/question/26287650

[2] 平方根倒数速算法_百度百科

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 巴山学长 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档