为什么需要 FPU 重置以防止 NaN 结果传播到下一个计算结果?



我有一个浮点计算,结果为 -1.#IND0000,导致随后的浮点计算为 -1.#IND0000。什么原因会导致这种情况?

编译器是Visual Studio C++ 2013 18.00 x86。如果我使用 GCC 版本 7.4.0,则不会出现问题。据我所知,重置 FPU 的状态不需要 FPU 交互。

代码首先执行一些计算,将值转换为定点。谷歌测试框架使用被测方法。我可以减轻计算中的数字错误(限制输入值,验证输出(,但后续计算应该是 -1.#INF0000;编译器错误?

最小示例:我在 RunCalcs(( 中执行计算,例如,t 为零,从而导致_SW_ZERODIVIDE。然后调用 CheckNextCalc(( 来执行两个不依赖于先前结果的后续计算。

将 FPU_RESET 设置为 1 包括在 RunCalcs(( 之后和 CheckNextCalc(( 之前对 _fpreset(( 的调用,结果 val1 的计算结果为 0.999981。当 FPU_RESET 为 0 时,val1 为 -1.#IND0000。

#define FPU_RESET 0
#define _USE_MATH_DEFINES
#include <iostream>
#include <cmath>
class CatchFloatEr
{
public:
void RunCalcs(const float t, const float r, unsigned int& val)
{
const float l = 1.0F / t;
val = static_cast<unsigned int>((l * static_cast<float>(0x10000)) + 0.5f);
}
void CheckNextCalc(float& val1, float& val2)
{
const float x1 = (2.0f * static_cast<float>(M_PI)) / static_cast<float>(1023);
val1 = cosf(x1);
const float x2 = (2.0f * static_cast<float>(M_PI)) / static_cast<float>(1023);
val2 = cosf(x2);     
}
};
int main(int argc, char *argv[])
{
CatchFloatEr cf;
for (unsigned int a = 0; a < 5; a++)
{
for (unsigned int b = 0; b < 5; b++)
{
unsigned int val;
cf.RunCalcs(0.0F, 0.0F, val);
//std::cout << a << b << " val = " << val << std::endl;
}
}
#if FPU_RESET
_fpreset();
#endif
float val1;
float val2;
cf.CheckNextCalc(val1, val2);
std::cout << "val1 = " << val1 << std::endl;
std::cout << "val2 = " << val2 << std::endl;
return 0;
}

结果

海湾合作委员会 7.4.0

FPU_RESET 0 and 1
g++ -Wall -pg -O0 -pedantic -o app main.cpp
val1 = 0.999981 
val2 = 0.999981
FPU_RESET 0/1
g++ -Wall -pg -O2 -pedantic -o app main.cpp
val1 = 0.999981 
val2 = 0.999981

Visual Studio 2013

FPU_RESET 0
val1 = -1.#IND    <--- Why would this result in -1.#INF0000
val2 = 0.999981
FPU_RESET 1
val1 = 0.999981 
val2 = 0.999981

旁白

我一直在使用 _statusfp(( 来读取标志位:

_SW_INEXACT     0x01   inexact (precision)
_SW_UNDERFLOW   0x02   underflow
_SW_OVERFLOW    0x04   overflow
_SW_ZERODIVIDE  0x08   zero divide
_SW_INVALID     0x10   invalid

如果我取消屏蔽 FPU 异常,我似乎会得到以下捕获与读取的标志匹配的多个错误。

STATUS_FLOAT_MULTIPLE_FAULTS     0xC00002B4L
STATUS_FLOAT_MULTIPLE_TRAPS      0xC00002B5L

启用 FP 可以通过以下方式完成:

_clearfp();
unsigned int currentState = 0U;
_controlfp_s(&currentState, 0U, _MCW_EM);
1.f / 0.f

不会导致NaN。它导致inf。但是,在后续计算中使用它可能会导致NaN,就像您的g一样。


我将尝试演示 FPU 异常状态如何不影响它所做的计算,但您使用的各个变量的状态会影响结果。 我将变量重命名为LG(因为l1看起来非常相似(。

#include <cfenv>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
std::string excstr(int excepts) {
static const std::map<int, std::string> names = {
{FE_DIVBYZERO, "FE_DIVBYZERO"}, {FE_INEXACT, "FE_INEXACT"},
{FE_INVALID, "FE_INVALID"},     {FE_OVERFLOW, "FE_OVERFLOW"},
{FE_UNDERFLOW, "FE_UNDERFLOW"},
};
std::string rv;
for(const auto& [mask, txt] : names) {
if(excepts & mask) {
if(rv.size()) rv += ", ";
rv += txt;
}
}
return rv;
}
template<typename T>
void check(const char* txt, const T& value) {
// taking "value" by-value would not guarantee a bit-perfect copy
std::cout << std::right << std::setw(13) << txt << " isnan: " << std::left
<< std::setw(5) << std::isnan(value) << " isinf: " << std::setw(5)
<< std::isinf(value) << " value: " << std::right << std::setw(5)
<< value << " ex: " << std::left
<< excstr(std::fetestexcept(FE_ALL_EXCEPT)) << "n";
}
int main() {
std::cout << std::boolalpha;
float L = 1.f / 0.f;
check("L=1/0", L);
std::feclearexcept(FE_ALL_EXCEPT); // clearing FE_DIVBYZERO
float G = L * 0.f; // L is inf regardless of the FPU state
check("G=L*0", G);
std::feclearexcept(FE_ALL_EXCEPT); // clearing FE_INVALID
check("cleared?", 0.f);
static_assert(static_cast<float>(0x10000) == 0x10000.p0);
// the two below calculations will set FE_INVALID again.
// casting inf to an unsigned int
auto lreg = static_cast<unsigned int>((L * 0x10000.p0) + 0.5f);
check("lreg", lreg);
// casting NaN to an unsigned int
auto greg = static_cast<unsigned int>((G * 0x10000.p0) + 0.5f);
check("greg", greg);
// the FPU doesn't need a reset to perform
// even though some exception bits are set:
float flreg = static_cast<float>(lreg) + 10.f;
check("flreg", flreg);
float fgreg = static_cast<float>(greg) + 2.f;
check("fgreg", fgreg);
float R = flreg / fgreg;
check("R=flreg/fgreg", R);
}

输出:

L=1/0 isnan: false isinf: true  value:   inf ex: FE_DIVBYZERO
G=L*0 isnan: true  isinf: false value:  -nan ex: FE_INVALID
cleared? isnan: false isinf: false value:     0 ex:
lreg isnan: false isinf: false value:     0 ex: FE_INVALID
greg isnan: false isinf: false value:     0 ex: FE_INVALID
flreg isnan: false isinf: false value:    10 ex: FE_INVALID
fgreg isnan: false isinf: false value:     2 ex: FE_INVALID
R=flreg/fgreg isnan: false isinf: false value:     5 ex: FE_INVALID

最新更新