2011年6月27日星期一

  世界上最快的浮点数开放算法

http://blog.csdn.net/WangSanHuai2010/archive/2010/05/08/5570730.aspx

任何一个3D引擎都是通过其内部的数学模型和实现工具来展现它的力量与速度的,Quake III中使用了一个非常有意思的技巧来计算平方根倒数(inverse square root)

Carmack's 不寻常平方根倒数
第一个跳出来的便是对函数Q_rsqrt中对0x5f3759df的使用,这个数计算了一个浮点数的inverse square root,但是为什么这个函数有这样的功能呢?
观察q_math.c原本的函数: 

01.float Q_rsqrt( float number )    
02.{  
03.  long i;   
04.  float x2, y;    
05.  const float threehalfs = 1.5F;    
06.  x2 = number * 0.5F;   
07.  y  = number;  
08.  i  = * ( long * ) &y;  // evil floating point bit level hacking    
09.  i  = 0x5f3759df - ( i >> 1 );  
10.  y  = * ( float * ) &i;  
11.  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration    
12.  y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed    
13.  return y;    
14.}   
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 );
  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; 

它不仅有效,甚至在某些CPU上,Carmack的Q_rsqrt 比(float)(1.0/sqrt(x)的计算快4倍,尽管sqrt()通常使用的是FSQRT的汇编指令!
在另一个文件code/common/cm_trace.c 中,我们发现了更简洁的对同样HACK的实现。这一次,它被用来计算一个float - sqrt(x)的平方根。注意,其中的唯一不同是在返回值上--用返回*y取代了返回y。 
01.float SquareRootFloat(float number) {  
02.    long i;   
03.    float x, y;   
04.    const float f = 1.5F;    
05.    x = number * 0.5F;  
06.    y  = number;   
07.    i  = * ( long * ) &y;    
08.    i  = 0x5f3759df - ( i >> 1 );    
09.    y  = * ( float * ) &i;    
10.    y  = y * ( f - ( x * y * y ) );    
11.    y  = y * ( f - ( x * y * y ) );    
12.    return number * y;    
13.}  
float SquareRootFloat(float number) {
    long i;
    float x, y;
    const float f = 1.5F; 
    x = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y; 
    i  = 0x5f3759df - ( i >> 1 ); 
    y  = * ( float * ) &i; 
    y  = y * ( f - ( x * y * y ) ); 
    y  = y * ( f - ( x * y * y ) ); 
    return number * y; 
}

牛顿对根的近似值
上面的代码执行了众所周知的牛顿对根的近似值,像绝大多数其它迭代求近似值的计算一样,牛顿近似值假定是迭代的;每一次迭代都增强了它的准确度直至达到需要的准确度。
在牛顿近似值中的一般想法是我们我们猜测一个数x的平方根值y,我们可能通过一个简单的操作用x/y来拉平y来取得更好的猜测,使其更接近实际的平方根,例如,我们像下面这样计算2的平方根,我们假定初始的猜测是1:
01.  2/1 = 2 ;  (2 + 1) / 2 = 1.5   
02.  2/1.5 = 1.3333; ( 1.5 + 1.3333 ) / 2 = 1.4167   
03.  2/1.4167 = 1.4117;  ( 1.4167 + 1.4117 ) / 2 = 1.4142   
04.  And so on...   
      2/1 = 2 ;  (2 + 1) / 2 = 1.5
      2/1.5 = 1.3333; ( 1.5 + 1.3333 ) / 2 = 1.4167
      2/1.4167 = 1.4117;  ( 1.4167 + 1.4117 ) / 2 = 1.4142
      And so on...  
如前面所提到的,牛顿的近似值是一个大家所熟知的用以快速计算平方根的方法。但是,Carmack在初始的猜测中就选取的不寻常的值,它彻底加强了准确度并且将Quake III中计算所要的值的迭代次数降到了1次!

魔数
这个函数中真正有意思的方面是神奇的常量0x5f3759df,用来计算初始猜测的,在
01.i  = 0x5f3759df - ( i >> 1 );   
i  = 0x5f3759df - ( i >> 1 ); 

因此,把输入除以2并从神奇常量中减去。这个常数工作起来几乎是完美的--对于一个 low relative error of 10^-3来说只要一次牛顿近似值迭代就够了。如评论中第二次迭代中展示的,这个近似值对Quake III引擎来说已经足够了。

结果,这个神奇的常数0x5f3759df成了一个迷了,在文章"Fast Inverse Square Root",普度大学的数学家Chris Lomont研究了这个常数,用了几种精细的技术,Lomont想自己用数学方法求出这个常数来,结果令人惊奇--Lomont用数学方法计算出来的最佳常数(0x5f37642f)有一点点不同,并且除了理论上强一些之外,它产生的结果并没有源代码中使用的原始常数好!确实,John Carmack 一定用了天才般的黑盒来找到这个常数。
只是仅仅从数字上来找的方法中,Lomont找到了一个更好的常数,这个数比原始的那个强了那么一点点。然而,实践中两个常数产生了大概相同的结果,Lomont提出这个使用了更好的常数的函数:

01.float InvSqrt(float x)    
02.{   
03.  float xhalf = 0.5f*x;   
04.  int i = *(int*)&x;   
05.  i = 0x5f375a86- (i>>1);   
06.  x = *(float*)&i;   
07.  x = x*(1.5f-xhalf*x*x);   
08.  return x;    
09.}   
float InvSqrt(float x) 
{
  float xhalf = 0.5f*x;
  int i = *(int*)&x;
  i = 0x5f375a86- (i>>1);
  x = *(float*)&i;
  x = x*(1.5f-xhalf*x*x);
  return x; 

-----------------------------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------------------

关于本文,东拼西凑来的,今天也已经很晚了。只是最近我在研究图形学算法的过程中,一些数字的数学的方法常常让我感到震惊,我惊叹于数学竟是如此的神奇。黑盒的应用是一门大学问,那些伟大的但是并不一定很出名的数学家常常利用黑盒另外开辟了区别于经典算法的道路,但却能带来非常人能想像的效率。在研究这些强人的作品的时候,能得到一种很美妙很利落的体会,好的算法就就像好的音乐,当欣赏他们的时候,只想拿起吉它,轻轻的弹上一首……

以其说计算机创造了21世纪,还不如说算法与数据结构创造了21世纪。前人的智慧也常让我感到无比的惊叹,而已往我却没有过这种深刻的体会,应视为较近距离接触了吧。最近在研究卡尔曼滤波器、小波变换理论、数据融合算法、以及复杂数据结构的过程中,真正体会到了什么才叫旷世绝学,虽然现在自己也只是略懂丁点儿,但这些来自前人的伟大思想,却让我振奋,让我更加明智。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/WangSanHuai2010/archive/2010/05/08/5570730.aspx

没有评论:

发表评论