原始算法
求某个数的平方根倒数是一个十分常见且高频的操作,比起直接调用数学库里的sqrt
函数,一个显著快速的方法是这样的
1 | float InvSqrt(float x){ |
实际上这个算法可以说是为了性能完全牺牲了解释性,因为绝大多数人第一次看到它的时候可能完全摸不着头脑。本文除了详细解释算法的原理以外,还希望揣测算法编写者的动机,并在其他的数值算法性能的的优化上给出指导。
原理
这个算法最关键的两个原理是浮点数的结构以及牛顿法数值迭代的原理。对于前者即算法中的第一步,要理解算法中对浮点数原始二进制数据直接转换为整型后的操作,就是要看对于整型
一个单精度浮点数由一位符号位,八位指数位以及二十三位小数位组成,若将一位符号位,指数位作为有符号8位整型,小数位作为无符号23位整型,并分别以
上述的符号位
观察到
同时考虑到
采用对数映射后得到的整型恰好有一个非常优越的性质,在计算平方根时可以直接对整型除以2得到其大致的在整型上的结果。但是目前这种映射实际上采用了过多近似,其精度不甚理想。在原有方法的基础上,我们可以把在原点的泰勒展开的近似增加一个偏置,在整个0与1之间的范围上精度能够更好。
将上述的一阶近似改写为
接下来如果令平方根倒数的估计值为
当
在已知上述估计值的情况下,进一步提高精度就要进行第二步,这里就是数值迭代方法中的经典方法牛顿法了。令
对应代码内返回前一行的迭代计算。实际上由于上述估计值已经比较接近真实值,牛顿法在此处收敛较快,在这里迭代一次的精度就已经足够了。
总的来说,计算平方根倒数首先是利用直接转换整型,并进行计算的操作的得到一个平方根倒数的估计值,然后再利用牛顿法迭代一次确保最终的精度。这个算法的显著特征就是速度极快,另外还需要优化的就是其精度,例如
动机
上面提到的是对算法本身原理的逆向讨论,即已知有人写出过这样的算法我们如何解释这个算法的原理。但更重要的了解编写者的动机,即怎么想到一个这样的算法能够极大地优化平方根倒数的计算。
在我看来,一个优化的关键是优化是借助于计算机浮点数结构完成的。同一段32位数据用浮点数表示和用整型表示,在我们看来数的意义是不一致的,而对于计算机来说是完全没有区别的。因此由于浮点数指数项的存在,借助这种转换关系可以完成一种对数/指数的映射。第二个优化的关键思路是在牛顿迭代法前,先用某种更加高效的手段估计一个不那么精确的值作为初始解,或者说牛顿法可以与其他方法组合使用。牛顿迭代法在根附近呈平方收敛,一个合适的初始值可以代替多次迭代达到相同的精度。
快速平方根倒数算法的类似计算方法同样可以计算平方根以及2n次方根等,另外整型的直接转换也为也是一种优化速度的新思路,如果针对定点计算以及要求通用性则可以采用CORDIC计算
参考
Fast inverse square root - wikipedia
IEEE 754 - wikipedia
CHRIS LOMONT - FAST INVERSE SQUARE ROOT