下面我改编了William Kahan 和 KC Ng于 1986 年编写的代码(查看底部的注释块),以产生 1 / sqrt(x) 的近似值,其中 x 是 IEEE-754 双精度浮点数。正是这个密码,Cleve Moler 和 Greg Walsh 改编成了以 Quake III 闻名的“快速反平方根”。
#include <stdio.h>
int main() {
double x = 3.1415926535;
double y, z;
unsigned long int x0, y0, k;
unsigned long long int xi, yi;
static int T2[64]= {
0x1500, 0x2ef8, 0x4d67, 0x6b02, 0x87be, 0xa395, 0xbe7a, 0xd866,
0xf14a, 0x1091b,0x11fcd,0x13552,0x14999,0x15c98,0x16e34,0x17e5f,
0x18d03,0x19a01,0x1a545,0x1ae8a,0x1b5c4,0x1bb01,0x1bfde,0x1c28d,
0x1c2de,0x1c0db,0x1ba73,0x1b11c,0x1a4b5,0x1953d,0x18266,0x16be0,
0x1683e,0x179d8,0x18a4d,0x19992,0x1a789,0x1b445,0x1bf61,0x1c989,
0x1d16d,0x1d77b,0x1dddf,0x1e2ad,0x1e5bf,0x1e6e8,0x1e654,0x1e3cd,
0x1df2a,0x1d635,0x1cb16,0x1be2c,0x1ae4e,0x19bde,0x1868e,0x16e2e,
0x1527f,0x1334a,0x11051,0xe951, 0xbe01, 0x8e0d, 0x5924, 0x1edd };
/* Convert double to unsigned long long (64 bits wide) */
xi = *(unsigned long long int*)&x;
/* Extract only the first half of the bits of the integer */
x0 = (xi & 0xffffffff00000000) >> 32;
/* Perform bit shift and subtract from an integer constant to get k */
k = 0x5fe80000 - (x0 >> 1);
/* T2 is indexed by mostly by exponent bits.
Only 5 highest bits from orig 52 in mantissa are used to index T2 */
y0 = k - T2[63 & (k >> 14)];
/* Pad with zeros for the LS 32 bits, convert back to long long */
yi = ((unsigned long long int) y0 << 32);
/* Get double from bits making up unsigned long long */
y = *(double*)&yi;
/* 1/sqrt(pi) ~ 0.564 */
printf("%lf\n", y);
return 0;
}
看起来它正在做与 Quake 版本相同的事情(减去 newton-raphson 步骤)
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;
}
我们首先将 double 的位转换为整数,这是我们开始使用的 double 的近似二进制对数(米切尔方法)。
我们将这些位右移并从一个常数中减去它们(对于这个比较,丢弃低 32 位并不重要),这是对数域中的近似除法。
下一步我不太清楚,因为查找表由非常少量的原始数字索引 - 主要是指数位。所以发生了更正,但我不确定如何解释它。
最后,我们将整数位(加上 LS 一半的 32 个 0)转换为浮点双精度,给我们一个近似的反对数。
所以我理解(或认为我理解)这里 4 个步骤中的 3 个,但是第三步——查找表在做什么,它是如何在双打范围内索引的,为什么?