0xff800001 和 0xffb00000 之间的长值重新解释为浮动错误

Long values between 0xff800001 and 0xffb00000 reinterpret to float with error

我在将二进制值转换为 float 时遇到问题,促使我进一步研究该问题。我发现 0xff8000010xffb00000 之间的值在被重新解释为 float 时具有它们的 15th 22nd LSB 位翻转。我为此使用的测试程序:

unsigned long long ca = 0;
unsigned long long cb = 0;
for(unsigned long long tmpLongLong = 0x00000000; tmpLongLong <= 0xffffffff; tmpLongLong++)
{
    unsigned long tmpLong1 = tmpLongLong;
    float tmpFloat = *(reinterpret_cast<float*>(&tmpLong1));
    unsigned long tmpLong2 = *(reinterpret_cast<unsigned long*>(&tmpFloat));
    ca++;
    if(tmpLong1 != tmpLong2)
    {
        cb++;
    }
    cout << (tmpLong2 == tmpLong1 ? "YES " : "NO ") << std::hex << tmpLong1 << " vs. " << tmpLong2 << std::dec << endl;
}
cout << "bad: " << cb << "/" << ca << " " << 100.0 / ca * cb << "%" << endl;

2 个损坏值的输出示例:

NO ff8003cf vs. ffc003cf
NO ff8003d0 vs. ffc003d0

这个问题的原因是什么,我该如何解决?

这是由于您的 C++ 实现屏蔽了 NaN 信号。

请注意,区别在于第 22 位,而不是问题中所述的第 15 位。比如前后值为ff8003cf16和ffc003cf16的情况下,log2(ff8003cf16−ffc003cf16) = 22.

当您的 C++ 实现将 float 值分配给 float object,并且该值是一个信号 NaN,它会设置位 22 使其成为一个安静的 NaN。

在二进制 32 格式的 IEEE-754 交换格式中(通常用于 float),如果指数位(30 到 23)全部打开并且有效位(22到 0) 不全为零。如果设置了有效数 (22) 的第一位,则它是一个安静的 NaN(使用时不会发出异常信号)。如果很清楚,它就是一个信号 NaN(使用时发出异常信号)。 (“信号”在 IEEE-754 中用于表示操作中发生了异常情况,而不是在改变程序控制流的 C++ 信号中使用,尽管这是 floating-point 的潜在结果信号。)

通常,将 float 值分配给 float object,如 float tmpFloat = *(reinterpret_cast<float*>(&tmpLong1)); 中发生的那样,被视为复制操作,不会改变值或信号异常。您的 C++ 实现似乎将其视为信号操作,因此分配信号 NaN 值会导致发出异常信号(可能会被忽略或可能会在 floating-point 异常标志中引发一个标志)并产生安静的 NaN因此。通过设置第 22 位将信号 NaN 转换为安静 NaN。

如果这是您的 C++ 实现正在做的事情,那么在分配 float 值时可能没有任何方法可以克服它。您可以通过复制代表它的字节(见下文)将所需的位放入 float 中,也可以通过复制将它们取出。但是,将 float 值用作 float 可能会导致信号 NaN 静音。

备注

请注意,通过重新解释其指针的转换来重新解释 object 的位是对 C++ 的滥用。一个常见的结果是,当使用结果指针时,这些位被重新解释为新类型。但是,C 标准不保证,正确的方法是将位复制到新的 object 中,如 float tmpFloat; std::memcpy(&tmpFloat, &tmpLong1, sizeof tmpFloat);。即将发布的 C++ 标准可能在 <bit> header 中声明了一个新的 std::bitcast<To, From>(const From &from),它将重新解释类型 To 中的 from 的位。