介于 0xff800001 和 0xffb00000 之间的长值重新解释为浮点数,但有误差



我在将二进制值转换为float时遇到了问题,促使我进一步研究这个问题。我发现0xff8000010xffb00000之间的值被重新解释为float它们的

15th 22 ndLSB 位翻转。我为此使用的测试程序:
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。例如,如果前后值为 ff8003cf 16 和 ffc003cf 16,则日志2(ff8003cf16−ffc003cf16) = 22。

当 C++ 实现将float值分配给float对象,并且该值是信令 NaN 时,它会设置 bit 22 以使其成为安静的 NaN。

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

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

如果这是您的C++实现正在执行的操作,则在分配float值时可能没有任何方法可以克服它。您可以通过复制表示float的字节(见下文)将所需的位放入中,也可以通过复制将它们取出。但是,任何使用float值作为float都可能导致沉默NaN信号。

笔记

请注意,通过重新解释对象的指针强制转换来重新解释对象的位是对C++的滥用。一个常见的结果是,当使用生成的指针时,位在新类型中重新解释。但是,C 标准不能保证,正确的方法是将位复制到新对象中,就像float tmpFloat; std::memcpy(&tmpFloat, &tmpLong1, sizeof tmpFloat);一样。即将推出的C++标准可能会在<bit>标头中声明一个新的std::bitcast<To, From>(const From &from),该将重新解释类型To中的from位。

最新更新