许多来源都描述了计算反平方根的“神奇”方法,可以追溯到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
因此,这一特定意见似乎违反了该文件中的主张。
我想知道这是纸张本身的问题,还是我的编码错误。我很感激任何反馈!
答案 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
评估错误并不会改变结果。