ULP比较码

ULP comparison code

以下代码片段散布在整个网络上,似乎在多个不同的项目中使用,几乎没有变化:

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);
}

参见,例如 here or here or here

但是,我不明白这是怎么回事。以我(也许是天真的)的理解,浮点成员变量 f 在构造函数中初始化,但整数成员 i 不是。

我不是很熟悉这里使用的二元运算符,但我不明白 uA.iuB.i 的访问如何产生除了随机数之外的任何东西,因为没有行该代码实际上以任何有意义的方式连接 fi 的值。

如果有人能告诉我这段代码为什么(以及如何)产生预期的结果,我将非常高兴!

很多未定义的行为在这里被利用。第一个假设是 union 的字段可以代替彼此访问,这本身就是 UB。此外,编码器假设:sizeof(int) == sizeof(float),浮点数具有给定的尾数和指数长度,所有联合成员都对齐到零,float 的二进制表示以非常特定的方式与 int 的二进制表示一致.简而言之,只要您在 x86 上,具有特定的 int 和 float 类型并且您在每个日出和日落时祈祷,这就会起作用。

您可能没有注意到这是一个联合,因此 int ifloat f 通常在大多数编译器的公共内存数组中以特定方式对齐。一般来说,这仍然是 UB,您甚至不能安全地假设将使用相同的内存物理位而不将自己限制在特定的编译器 特定的体系结构中。可以保证的是,两个成员的地址将相同(但可能存在对齐 and/or 类型问题)。假设你的编译器使用相同的物理位(这绝不是标准保证的)并且它们都从偏移量 0 开始并且具有相同的大小,那么 i 将代表 f 的二进制存储格式.. 只要您的体系结构 没有任何变化 。忠告的话?除非没有必要,否则不要使用它。坚持 AlmostEquals() 的浮点运算,你可以这样实现。当我们考虑这些特性时,这是优化的最后一步,我们通常在一个单独的分支中进行,你不应该围绕它来规划你的代码。