我试图理解以下摘自here的代码段
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 ); // ???
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;
}
我不明白的是从float到long指针再回到float指针的转换。为什么我们不能简单地做i=y
而不是先引用然后取消引用浮点数。
我是指针转换的新手,所以请多多包涵。
答案 0 :(得分:2)
此代码片段显然是快速反平方根。指针语义实际上并没有真正用来做指针事情,而是将某个内存位置的位重新解释为另一种类型。
如果您要分配i=y
,它将被转换为从浮点到整数的截断转换。但是,这不是这里想要的。您真正想要的是原始对位的访问,这对于浮点类型的变量来说不是直接可能的。
让我们分解一下这句话:
i = * ( long * ) &y;
&y
:y的地址。此表达式的类型为(float*)
。
(long*)
:强制键入。 Appled &y
会滚动显示信息,该信息是浮点型对象的地址。
*
:取消引用,这意味着“读出”位于给定地址处的所有内容,并解释为要取消引用的指针的基本类型。我们已经将其改写为(long*)
,实际上是对编译器的欺骗。
出于所有意图和目的,这会破坏指针别名规则并调用未定义的行为。您不应该这样做(请注意使用小孔¹)。
进行这种欺骗的某种定义明确的方法(至少不会破坏指针别名规则)是通过union
来实现的。
float Q_rsqrt( float number )
{
union {
float y;
long i;
} fl;
float x2;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
fl.y = number;
fl.i = 0x5f3759df - ( fl.i >> 1 ); // ???
fl.y = fl.y * ( threehalfs - ( x2 * fl.y * fl.y ) ); // 1st iteration
// fl.y = fl.y * ( threehalfs - ( x2 * fl.y * fl.y ) ); // 2nd iteration, this can be removed
return fl.y;
}
编辑:
应该注意的是,如上所述的通过联合进行类型修饰并不受C语言标准的认可。但是,到目前为止,与语言未定义行为不同,该标准将以这种方式完成的联合访问的详细信息保留为依赖于实现的行为。由于类型绑定是某些任务所必需的,因此,我认为已经提出了一些建议,以使其在即将到来的C编程语言标准中得到很好的定义。
对于所有意图和目的,几乎所有编译器都支持上述方案,而如果启用所有优化路径,则通过指针强制类型转换将导致奇怪的事情发生。
1:一些编译器(用于特定语言扩展的旧版或自定义编写的CUDA nvcc)已经严重损坏,您实际上必须强迫他们这样做以完成您想要的事情。
答案 1 :(得分:1)
好的,因此,从浮点处理器运行缓慢或不存在时起,您就在研究一些古老的黑客技术。我怀疑原始作者会捍卫继续使用它。它也不符合现代语言的透明性要求(即“未定义的行为”),因此可能无法移植到所有编译器或解释器中,也可能无法通过lint和valgrind等优质工具正确处理,但这是可以的快速编码是在80年代和90年代写的。
在位级别,所有内容都存储为字节。 long存储在4个字节中,浮点数也存储在4个字节中。但是,这些位的区别很大。以整数/长为单位,每个位的排名相似为2的幂,并且可以用作位字段。在浮点数中,一些位用于表示应用于该数字其余部分的指数。有关更多信息,请阅读IEEE。
此技巧采用浮点值,并像对待整数位字段一样查看字节,因此可以应用魔术。它会查看结果字节,好像它们又是浮点数一样。
我不知道那魔术到底是什么。没有人做,甚至没有人写,因为它没有被评论。另一方面,毁灭和地震的源头确实曾经是邪教的阅读者,所以也许有人还记得细节吗?
在“过去的好时光”中曾经有很多这样的技巧,但是现在相对没有必要了,因为浮点数已经内置在主处理器中,并且其速度甚至快于整数运算。最初,即使是从协处理器上载和下载小整数,也可以比使用内置方法更快地完成。