为什么需要FPU复位才能防止NaN结果传播到下一个计算结果?

时间:2019-08-28 16:04:40

标签: c++ exception floating-point fpu msvc12

我有一个浮点计算,结果为-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;
}

结果

GCC 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 个答案:

答案 0 :(得分:2)

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