侧边栏壁纸
博主头像
Kefei的记事本博主等级

好脑瓜不如烂笔头

  • 累计撰写 219 篇文章
  • 累计创建 11 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

“神秘的0x5F3759DF:揭秘平方根倒数快速算法”

Administrator
2024-06-21 / 0 评论 / 0 点赞 / 7 阅读 / 5657 字

https://mp.weixin.qq.com/s/XbSiJ5l7MmOAu18LoqVRCA

https://www.bilibili.com/video/BV18j411i7bp/?spm_id_from=333.880.my_history.page.click

John Carmack(约翰·卡马克)这个名字也许玩家听起来比较陌生,但如果说到他的贡献相信大家就不陌生了。如今我们玩到的FPS和3A大作都是这位大神的功劳,毫不夸张的说卡马克可谓是游戏发展史上上的3D引擎之父、程序员之神。他的FPS设计理念以及引擎构架,为整个游戏市场开辟出了一条新的道路,几乎市面上所有的第一人称射击游戏,都受到了他的影响。

约翰·卡马克

“what the fuck?”这句婉转优美的注释正是出自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;                       // 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;

}

而这句注释指向的代码只有短短的四行,但就这4行代码却永远的改变了电脑游戏,影响了后续的整个程序世界。

可以说这段代码将程序员和300年前的牛顿来了一波梦幻联动

牛顿用极度优雅的方式将二进制和微积分的思想的精华做了浓缩

下面让我们来了解让卡马克给出锐评的“平方根倒数快速算法”

平方根倒数快速算法(Fast Inverse Square Root),又称为“快速平方根倒数算法”或“0x5f3759df算法”,是一种用于计算数值的平方根倒数(1/√x)的快速算法。这个算法最著名的实现是在1999年发布的计算机游戏《雷神之锤III竞技场》(Quake III Arena)中。这个算法的关键在于利用浮点数的二进制表示和一个神奇数字(0x5f3759df)来得到一个接近真实平方根倒数的初始近似值,然后通过牛顿迭代法(Newton's method)进行精细调整。

下面是这个算法解释:

float Q_rsqrt( float number )

{

  long i;

  float x2, y;

  const float threehalfs = 1.5F;

  x2 = number * 0.5F;

  y = number;

  i =  ( long  ) &y;          // 获取x的二进制表示

  i = 0x5f3759df - (i >> 1);    // 初始近似值的计算

  y =  ( float  ) &i;        // 将计算结果解释为浮点数

  y = y  (threehalfs - ( x2  y * y ) );   // 牛顿迭代

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

  return y;

}

推导过程涉及对IEEE 754标准的浮点数表示的理解。在IEEE 754标准中,一个浮点数由符号位、指数位和尾数位组成。对于32位的浮点数(float),其结构如下:

  • 符号位:1位

  • 指数位:8位

  • 尾数位:23位

推导的关键步骤如下:

初始近似值:

通过对浮点数的二进制表示进行操作,算法初始生成一个近似值。这个操作基于这样的观察:对于32位浮点数,平方根倒数的指数部分大概是原数指数的一半。因此,通过将原数的二进制表示右移一位并从一个特定的常数中减去,可以得到一个较为合理的初始近似值。这个特定的常数就是0x5f3759df。

牛顿迭代:

初始近似值会通过牛顿迭代法进行精细调整。牛顿迭代法是一种迭代求解函数零点的方法。在这个场景中,我们想要求解f(x) = 1/x^2 - S = 0这个函数的零点,其中S是我们要计算平方根倒数的数。对于f(x),牛顿迭代的迭代公式为:

x_{n+1} = x_n - f(x_n) / f'(x_n)

将f(x)代入并简化后,我们得到了更新近似值的公式:

x_{n+1} = x_n  (1.5 - xhalf  x_n * x_n)

其中xhalf是原数x的一半。这个迭代公式会迅速收敛到真实的平方根倒数。

这个算法的神奇之处在于,尽管它的起点是一个基于经验的近似,但它通常能在只进行一次迭代的情况下提供足够精确的结果,这使得它在计算密集型应用中非常有用,比如图形处理。

然而,由于现代处理器和编译器的优化,以及数学库的高效实现,这个算法的实际应用已经不如以前广泛。现代计算机系统可以更快、更准确地计算平方根倒数。尽管如此,这个算法在计算机科学历史上仍然是一个有趣的趣闻。

代码中的这两行:

  y = y  (threehalfs - ( x2  y * y ) );   // 牛顿迭代

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

两行代码实际上是相同的,它们都是牛顿迭代法的一个步骤,用于精细调整初始近似值,以更接近真实的平方根倒数。这个迭代步骤是基于牛顿-拉弗森迭代法(也称作牛顿-拉弗森迭代除法),它是牛顿迭代法的一个特例,用于快速找到函数f(y) = 1/y^2 - x的零点,其中x是我们想要计算其平方根倒数的数。

这个迭代步骤的数学表达式为:

y_{n+1} = y_n  (1.5 - x  y_n^2 / 2)

这里的threehalfs代表常数1.5,x2是原始数x的一半,y是当前的近似值。这个迭代公式可以从牛顿迭代法的一般形式导出:

y_{n+1} = y_n - f(y_n) / f'(y_n)

对于f(y) = 1/y^2 - x,其导数f'(y) = -2/y^3,代入后得到:

y_{n+1} = y_n - (1/y_n^2 - x) / (-2/y_n^3)

y_{n+1} = y_n + y_n / 2 - x * y_n^3 / 2

y_{n+1} = y_n  (1.5 - x  y_n^2 / 2)

这是牛顿迭代法中的一个迭代步骤。在代码中,这一步骤被重复执行了两次,这通常是为了提高精度。在实际应用中,一次迭代通常已经足够接近真实值,但两次迭代可以进一步提高结果的准确性。在实际使用中,你会根据所需的准确度来决定执行几次迭代。通常情况下,一到两次迭代已经足够产生一个非常接近实际值的结果。

0

评论区