平方根倒数速算法(卡马克开方法)

平方根倒数速算法(Fast inverse square root),经常和一个十六进制的常量 0x5f3759df联系起来。该算法被用来快速运算平方根倒数,速度是 float(1/sqrt(x)) 方法的4倍。该算法大概由上个世纪90年代的硅图公司开发出来,后来出现在John Carmark的Quake III Arena的源码中。

这是一个古老的算法,最早的讨论见于2001年中国的CSDN论坛上。并且该段代码可能已经不适用于当代的64bits机器,因为现在的64bits的机器上 long 型数据长度已经从4字节变成了8字节。但是我觉得,一代代新人对于一些老的经典的问题的讨论是有实际应用价值以外的意义的。仅此而已,不做争辩。

代码总览

下面的快速平方根倒数代码来自Quake III Arena

 1 float Q_rsqrt( float number )
 2 {
 3     long i;
 4     float x2, y;
 5     const float threehalfs = 1.5F;
 6
 7     x2 = number * 0.5F;
 8     y  = number;
 9     i  = * ( long * ) &y;                       // evil floating point bit level hacking
10     i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
11     y  = * ( float * ) &i;
12     y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
13 //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
14
15     return y;
16 }

为了求解平方根的倒数值,首先需要产生一个近似值。然后在利用数值计算的方法来减小近似值的误差。近似值的选取很大程度上影响后面数值计算迭代次数。在这之前,这个近似值的选取是通过查表得到的,利用所谓的lookup table。但时上面提供了比查表还要迅速的方法,并且速度是常规计算平方根倒数的四倍。虽然该方法在精度上有一定的损失,但时计算速度上弥补了这一点的不足。该算法是为IEEE 754标准下的浮点数存储规则设计的,Chris Lomot和Charles McEniry展示了其在其他浮点存储标准下的运用。

算法运行示例

这里有一个例子,令x = 0.15625,我们想计算的值。程序的第一步如下:

0011_1110_0010_0000_0000_0000_0000_0000  //x和i的各比特位表示
0001_1111_0001_0000_0000_0000_0000_0000  //右移一位后的结果: (i >> 1)
0101_1111_0011_0111_0101_1001_1101_1111  //magic number 0x5f3759df
0100_0000_0010_0111_0101_1001_1101_1111  //0x5f3759df - (i >> 1)的结果

将上述的数如果是用IEEE 754标准存储的浮点数的话,各个数值大小如下:

0_01111100_01000000000000000000000  //1.25 * 2^-3
0_00111110_00100000000000000000000  //1.125 * 2^-65
0_10111110_01101110101100111011111  //1.432430... * 2^+63
0_10000000_01001110101100111011111  //1.307430... * 2^+1

我们神奇的发现最后的数字已经很接近准确值2.52982了!what the fuck, why??

算法原理

该算法通过下面几个步骤计算的值:

  1. 将32bits的浮点型变量reinterpret成整型变量,用于计算log2(x)的近似值
  2. 用上述的近似值计算log2(1/√x)的近似值
  3. 将上述的计算结果强转回32bits浮点型变量,得到迭代初值
  4. 经过一次简单的牛顿迭代,得到足够高精度的结果
  • 将浮点数型数据强制转换成整型,近似求解对数值

前面一篇博文专门介绍了IEEE 754标准下的浮点数表示方法,下面简单的回顾一下。为了能够存储非零的浮点数,第一步就是将浮点数x写成标准化的二进制形式:

这里ex是整数,mx ∈ [0, 1)显而易见。我们把1.b1b2b3...称为尾数1 + mx的二进制表示,其中小数点左边的1称为前导数位。很显然,前导数位始终为1,所以在浮点数的存储过程中将其忽略。

我们现在来计算三个数:

  • Sx,符号位。当x>0,等于0,x<0,等于1;
  • Ex = ex + B,偏移指数。为了既能存储正指数,又能存储负指数。IEEE 754标准在真实的指数上加一个偏移量来作为存储指数,对于单精度浮点数来说B=127;
  • Mx = mx × LL = 223

首先一个被开方数肯定是个非负数,所以符号位一定为0,那么,对其取以2为底的对数可得。因为mx ∈ [0, 1),所以有。其中σ是用来调节近似的精确度的,当σ ≈ 0.0430357时可以获得最优的近似结果。所以我们有

如果我们把浮点数强制转换成整数的话,偏移指数变成了这个整数的高8位,尾数部分变成了整数的低23位。

这个整数的数值为:

从上式我们可以看出,整数值Ix其实是对log2(x)平移和伸缩后的结果,并且很容易把log2(x)重新表示出来:

  • 迭代初值的计算

好的,到现在为止我们知道了将浮点数强转成整数发生了什么。卡马克开方法最重要的一步就是找出了一个迭代初值,这个初值神接近真实结果,使得迭代收敛速度奇快。那么我们接下来将要看到,所谓的Magic Number的来历,以及这个迭代初值是怎么求得的。

令y = 1/√x ,然后就有

根据上面刚刚得到的结论有:

整理后得到:

我们发现了什么?将平方根倒数和被开方数强转成整数竟然有这种近似关系!所以所谓的Magic Number就是3/2L(B-σ)的十六进制写法,所以就有了如下的代码:

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

第二项是通过右移操作计算½ Ix

  • 牛顿迭代法

通过上述的操作后,该算法将求得的整数重新强转回浮点数,这样就得到了迭代的初始值。下面再来看看迭代是如何进行的。

y就是就是下面方程的解:

牛顿迭代法给出了下面的方法来迭代求解方程的根:

其中是前一次迭代的结果,对于第一次迭代来说就是迭代初值。所以上式写成代码的形式如下:

y = y*(threehalfs - x2*y*y);

重复上面的步骤,将每次计算的结果作为输入,就会不断逼近真实结果。

由于我们通过前面的方法选取的初始值已经特别接近真实值了,没有必要进行更多次的迭代,因此源码中甚至将第二次迭代的过程都注释掉了。

Reference:

[1] https://en.wikipedia.org/wiki/Fast_inverse_square_root

时间: 2024-09-28 09:51:50

平方根倒数速算法(卡马克开方法)的相关文章

Fast InvSqrt()(平方根倒数速算法)

浮点数的平方根倒数常用于计算正规化矢量.3D图形程序需要使用正规化矢量来实现光照和投影效果,因此每秒都需要做上百万次平方根倒数运算,而在处理坐标转换与光源的专用硬件设备出现前,这些计算都由软件完成,计算速度亦相当之慢.在1990年代这段代码开发出来之时,多数浮点数操作的速度更是远远滞后于整数操作.因而针对正规化矢量算法的优化就显得尤为重要.下面陈述计算正规化矢量的原理: 要将一个矢量标准化,就必须计算其欧几里德范数,以求得矢量长度,为此便需对矢量的各分量的平方和求平方根:而当求取到其长度,并以之

求平方根的倒数速算法--向卡马克等人致敬

昨日,风雨交加,气温骤降,所有人都蜷缩在不暖和的厚衣服里,无神的盯着显示器.我也不例外,颤抖的手点击着鼠标,一边埋怨这天气,一边埋怨这电脑.突然,一段代码映入眼帘,定睛一看,没看懂,代码是这样的: float Q_rsqrt( float number ) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = * ( long * ) &y; // 这TM是啥 i = 0

sqrt函数实现之卡马克方法

sqrt函数的实现主要有三种方式: 二分法 牛顿法 卡马克方法 这里主要介绍高效的卡马克方法.卡马克方法起源于<雷神之锤III竞技场>中使用的平方根倒数速算法,下列代码是平方根倒数速算法在<雷神之锤III竞技场>源代码中的应用实例.示例剥离了C语言预处理器的指令,但附上了原有的注释: float Q_rsqrt( float number ) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.

卡马克算法(地图重复利用,跑酷类游戏)

----------------------------下面是理论知识-------------------------- 卡马克算法:由约翰·卡马克(John Carmack)开发的一种游戏地图处理方法,被广泛运用到2D卷轴式游戏和手机游戏中.约翰·卡马克:id Software创始人之一,技术总监.享誉世界的著名程序员,以卡马克算法和3D游戏引擎开发而闻名世界,被奉为游戏行业偶像.同时他也是个全面型的技术天才,现在致力于民用航天器开发,是民用航天器开发小组Armadillo Aerospac

速求平方根倒数

在游戏3D建模方面很多时候要用到求平方根的倒数,而本文章打算介绍的算法会比正常算法快上4倍左右.这对于产品性能将是一个大幅度的提高. 那我们要从哪里开始呢?首先不得不提一提 idsoftware.这是一个创建之初只有13个人的小公司,但它推出的毁灭战士(DOOM)系列游戏可以说改变了游戏世界,极大地推动了游戏产业的发展,因为在当时贫瘠的电脑性能的支撑下,一个开发者能够在游戏中加入一段流畅的动画都会让人惊叹不已,而我们所说的idsoftware,在那个年代就已经做出了画面远超同代其余作品的游戏,像

卡马克卷轴算法研究

对于J2ME框架下的手机游戏程序的开发,其地图滚动的重绘有多种算法,由于手机性能的限制和开发周期等其他非技术条件,需要根据情况灵活选择所需的技术.但在及其苛刻条件下,如系统CPU资源不足,地图块尺寸较小等,会造成屏幕闪耀,帧数过低等情况,严重影响到游戏体验.在开发中如此类问题无法绕过以及避免(指通过修改策划方案,以及程序使用的技术框架),则需要考虑使用地图缓冲绘制技术,卡马克卷轴就是一种最经典的地图缓冲绘制技术.可有效的改善在地图绘制中的屏幕闪耀,帧数过低等情况. English Abstrac

算法数据结构面试分享(一)- 解决算法问题的一般方法

先看一道题目: 给你一个整型数组,我想找出来最大的两个数,能帮我写一个算法吗?     拿到这个题目,大家会怎么想到用什么方法解决吗?我见过很多同学的回答是,先排序,取最大的两个数就好了.那么接下来我们的问题就变成了如何给这个整型数组排序了.我们有很多种方法,冒泡排序,快速排序等等.很有可能面试官就让你开始写具体的排序算法了.当然,有些有经验的同学可能会说了,排序我直接调用sort方法就好了哈.  其实,这两种情况都没有对错之分,只是没有敲开面试官的心扉,也没有给人眼前一亮,让自己脱颖而出. 再

Android手机外置SD卡(TF卡)的获取方法

Android手机上的外置SD卡,起初的时候,即在Android出世的前几年,那时手机的存储是十分有限的,不像现在到处可见16G.32G和64G的存储,因而那时候的手机有的厂商允许插入外置的SD卡,此时这张卡仍处于手机的扩展部分.后来,随着手机的发展以及存储能力的增加,这张外置SD卡,逐渐成为了手机的一部分,不再允许可挺拔了,当然现在依然有的手机允许对存储进行拓展,比如三星等. 那张拓展的存储卡,现在叫做TF卡,且不是所有的手机都支持它,但是有时候有些奇葩需求偏要优先存储在TF卡里面,这叫不得不

C# 不卡屏延时方法,延迟系统时间,但系统又能同时能执行其它任务

//延迟系统时间,但系统又能同时能执行其它任务,不卡屏延时方法 public static void Delay(int milliSecond) { int start = Environment.TickCount; while (Math.Abs(Environment.TickCount - start) < milliSecond) { Application.DoEvents();//转让控制权 } }