Quake逆平方根:准确度

时间:2017-01-04 07:15:51

标签: c floating-point floating-accuracy square-root

许多来源都描述了计算反平方根的“神奇”方法,可以追溯到Quake游戏。维基百科有一篇很好的文章:https://en.wikipedia.org/wiki/Fast_inverse_square_root

我特别发现以下内容是对算法的一个非常好的写作和分析:https://cs.uwaterloo.ca/~m32rober/rsqrt.pdf

我试图在本文中复制其中一些结果,但是准确性存在问题。用C编码的算法如下:

#include <math.h>
#include <stdio.h>

float Q_rsqrt(float number) {
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y = number;
  i = *(long *) &y;
  i = 0x5f3759df - (i >> 1);
  y = *(float *) &i;
  y = y * (threehalfs - (x2 * y * y));
  // y = y * (threehalfs - (x2 * y * y));
  return y;
}

paper表示对于所有正常正常浮点数,相对误差最多为0.0017522874。 (代码见附录2,1.4节讨论。)

然而,当我“插入”数字1.4569335e-2F时,我得到的错误大于预测的容差:

int main ()
{

  float f = 1.4569335e-2F;

  double tolerance = 0.0017522874;
  double actual    = 1.0 / sqrt(f);
  float  magic     = Q_rsqrt(f);
  double err       = fabs (sqrt(f) * (double) magic - 1);

  printf("Input    : %a\n", f);
  printf("Actual   : %a\n", actual);
  printf("Magic    : %a\n", magic);
  printf("Err      : %a\n", err);
  printf("Tolerance: %a\n", tolerance);
  printf("Passes   : %d\n", err <= tolerance);

  return 0;
}

输出结果为:

Input    : 0x1.dd687p-7
Actual   : 0x1.091cc953ea828p+3
Magic    : 0x1.08a5dcp+3
Err      : 0x1.cb5b716b7b6p-10
Tolerance: 0x1.cb5a044e0581p-10
Passes   : 0

因此,这一特定意见似乎违反了该文件中的主张。

我想知道这是纸张本身的问题,还是我的编码错误。我很感激任何反馈!

2 个答案:

答案 0 :(得分:3)

您正在使用错误的幻数。

0x5f3759df是最初在Quake III中使用的值,但后来发现0x5f375a86提供了更好的结果。如果你看一下你引用的论文第40页的图6.1,你会发现它使用了改进的常数。

以下是我使用0x5f375a86获得的结果:

Input    : 0x1.dd687p-7
Actual   : 0x1.091cc953ea828p+3
Magic    : 0x1.08a5fap+3
Err      : 0x1.cae79153f2cp-10
Tolerance: 0x1.cb5a044e0581p-10
Passes   : 1

答案 1 :(得分:1)

让我们尝试一小段代码重新计算相对错误的界限,并显示它略大于thesis of Matthew Robertson中的那个。实际上,正如@ the thesis of Matthew Robertson中@squeamishossifrage的回答中首先注意到的那样,这个实现是在Quake III的源代码中公开的实现。特别是,Quake III常量的原始值可以在Quake III的源代码中找到,在第561行的文件q_math.c中。

首先,代码需要适应64位平台。唯一可能需要修改的是整数类型:long不是与平台无关的。在我的linux计算机上,sizeof(long)返回8 ...如第49页的文章中所述,类型uint32_t将确保整数的类型与{{1}的大小相同}。

以下代码由float编译并由gcc main.c -o main -lm -Wall运行:

./main

对于绑定,我获得了#include <math.h> #include <stdio.h> #include <inttypes.h> float Q_rsqrt(float number) { uint32_t i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = *(uint32_t *) &y; i = 0x5f3759df - (i >> 1); // 0x5f3759df 0x5f375a86 y = *(float *) &i; y = y * (threehalfs - (x2 * y * y)); // y = y * (threehalfs - (x2 * y * y)); return y; } int main () { printf("%ld %ld\n",sizeof(long),sizeof(uint32_t)); uint32_t i; float y; double e, max = 0.0; float maxval=0; for(i = 0x0000000; i < 0x6f800000; i++) { y = *(float *) &i; if(y>1e-30){ e = fabs(sqrt((double)y)*(double)Q_rsqrt(y) - 1); if(e > max){ max = e; maxval=y; } } } printf("On value %2.8g == %a\n", maxval, maxval); printf("The bound is %2.12g == %a\n", max, max); return 0; } 。正如您所注意到的,它略大于文章中报道的那个(0.0017523386721 == 0x1.cb5d752717ep-10)。使用0.001752287代替float评估错误并不会改变结果。