许多来源都描述了计算反平方根的“魔术”方法,该方法显然可以追溯到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
因此,此特定输入似乎违反了该论文中提出的主张。
我想知道这是否是纸张本身的问题,还是我在编码中犯了一个错误。我会很感激任何反馈!
最佳答案
让我们尝试一些代码来重新计算相对误差的界限,并显示它比thesis of Matthew Robertson中的误差稍大。实际上,正如在@squeamishossifrage的答案中首先注意到并在the thesis of Matthew Robertson中指出的那样,此实现是Quake III的源代码中公开的一种实现。特别是,在第561行的文件q_math.c中,可以在Quake III的源代码中找到Quake III常数的原始值。
首先,需要对代码进行修改以使其在64位平台上工作。唯一需要修改的是整数类型:long
与平台无关。在我的Linux计算机上,sizeof(long)
返回8 ...在第49页的论文中进行了更新,uint32_t
类型将确保整数的类型与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
而不是double
评估错误不会大大改变结果。