0xff800001 和 0xffb00000 之间的长值重新解释为浮动错误
Long values between 0xff800001 and 0xffb00000 reinterpret to float with error
我在将二进制值转换为 float
时遇到问题,促使我进一步研究该问题。我发现 0xff800001
和 0xffb00000
之间的值在被重新解释为 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
的位。
我在将二进制值转换为 float
时遇到问题,促使我进一步研究该问题。我发现 0xff800001
和 0xffb00000
之间的值在被重新解释为 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
的位。