我在将二进制值转换为float
时遇到了问题,促使我进一步研究这个问题。我发现0xff800001
和0xffb00000
之间的值被重新解释为float
它们的
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
位。