以下代码段分散在整个网络上,似乎在多个不同的项目中使用,几乎没有变化:
union Float_t {
Float_t(float num = 0.0f) : f(num) {}
// Portable extraction of components.
bool Negative() const { return (i >> 31) != 0; }
int RawMantissa() const { return i & ((1 << 23) - 1); }
int RawExponent() const { return (i >> 23) & 0xFF; }
int i;
float f;
};
inline bool AlmostEqualUlpsAndAbs(float A, float B, float maxDiff, int maxUlpsDiff)
{
// Check if the numbers are really close -- needed
// when comparing numbers near zero.
float absDiff = std::fabs(A - B);
if (absDiff <= maxDiff)
return true;
Float_t uA(A);
Float_t uB(B);
// Different signs means they do not match.
if (uA.Negative() != uB.Negative())
return false;
// Find the difference in ULPs.
return (std::abs(uA.i - uB.i) <= maxUlpsDiff);
}
但是,我不明白这里发生了什么。对于我的(可能是天真的)理解,浮点成员变量f
在构造函数中初始化,但整数成员i
不是。
我对这里使用的二元运算符并不十分熟悉,但我无法理解uA.i
和uB.i
的访问如何产生除随机数之外的任何内容,前提是代码中没有行实际上以任何有意义的方式连接f
和i
的值。
如果有人可以告诉我为什么(以及如何)这段代码产生了预期的结果,我会非常高兴!
答案 0 :(得分:4)
这里正在利用许多未定义的行为。第一个假设是联合字段可以代替彼此访问,这本身就是UB。此外,编码器假设:sizeof(int) == sizeof(float)
,浮点数具有给定长度的尾数和指数,所有联合成员都对齐为零,浮点数的二进制表示符与二进制表示符以非常特定的方式与int重合。简而言之,只要您使用的是x86,具有特定的int和float类型,并且在每个日出和日落时都要祷告,这将会有效。
您可能没有注意到的是这是一个联合,因此int i
和float f
通常由大多数编译器以特定方式在公共内存数组中对齐。通常,这仍然是UB,您甚至无法安全地假设将使用相同的物理内存位而不会将您自己限制在特定的编译器和特定的体系结构中。所有这些都得到保证,两个成员的地址将是相同的(但可能存在对齐和/或类型问题)。假设您的编译器使用相同的物理位(标准无法保证)并且它们都从偏移量0开始并具有相同的大小,那么i
将表示f
的二进制存储格式..只要 nothing 在您的架构中发生变化。忠告?在你不需要之前不要使用它。坚持AlmostEquals()
的浮点运算,你可以像这样实现它。当我们考虑这些专业并且我们通常在一个单独的分支中进行优化时,这是优化的最后一步,你不应该围绕它来规划你的代码。