如何在C语言中检测浮点数下溢

4

我需要帮助和示例代码,以标准方式检测有符号和无符号的浮点数下溢,不使用第三方库和异常。我在谷歌上搜索并发现各种作者都在谈论“渐进下溢是否算作下溢”?有人能解释一下什么是渐进下溢吗?我需要检查这两种情况。

感谢您的时间和帮助。


@神秘请求,请提供一个示例代码,说明我们如何实现这个。 - venkysmarty
3
《计算机科学家应知道的浮点运算》是必读的,它也涵盖了“渐进式下溢(gradual underflow)”。 - Alexey Frunze
3
请勿创建重复的问题。 - Alexey Frunze
这不是重复的问题。这个问题特别涉及C语言。链接的“重复”问题特别涉及C++。 - SO Stinks
@sos 这个问题被标记为 [tag:c] 和 [tag:c++]。重复的答案提供了 C 和 C++ 的解决方案。这是否算作重复很难说。毕竟,这个问题实际上是两个问题。 - IInspectable
显示剩余3条评论
4个回答

3
查阅“gradual underflow”的前几个搜索结果并没有明确直接的答案,因此:

IEEE-754二进制浮点数在其大部分范围内具有正则模式:包含一个符号位、一个指数和一个具有固定位数的尾数(32位float为24位,64位double为53位)。但是,在两端必须打断这种模式。在高端,超出最大指数的结果会变成无穷大。在低端,我们可以做出选择。

一种选择是,如果结果小于最低指数,则将结果舍入为零。然而,IEEE-754使用了一种称为渐近下溢的不同方案。最低指数保留给不同于常规指数所用格式的尾数。

对于普通的指数,24位尾数为“1.”,后跟23位编码为尾数字段。当数字为次正常值时,指数具有与最低常规指数相同的值,但24位尾数为“0.”,后跟23位。这是渐进下溢,因为随着数字变得越来越小,它们的精度越来越低(尾数中前导位的零越多),直到达到零。

渐进下溢具有一些良好的数学特性,尤其是a-b == 0当且仅当a == b。对于突然下溢,如果a和b不同,则可能会出现a-b == 0的情况,因为a-b太小而无法在浮点格式中表示。对于渐进下溢,对于较小的ab,所有可能的a-b值都是可表示的,因为它们只是具有最低指数的尾数之间的差异。

确定浮点下溢是否发生的另一个问题是,实现允许(由IEEE-754标准)基于舍入前或舍入后的测试报告下溢。在计算结果时,浮点实现实际上必须执行以下步骤:

  • 计算确切结果的符号、指数和尾数。
  • 将结果四舍五入以适合浮点格式。如果尾数四舍五入,则可能会增加指数。

该标准允许实现使用以下任一测试报告下溢:

  • 计算确切结果的符号、指数和尾数。
  • 指数是否小于正常范围?如果是,则报告下溢。
  • 将结果四舍五入以适合浮点格式。如果尾数四舍五入,则可能会增加指数。

或:

  • 计算准确结果的符号、指数和尾数。
  • 将结果舍入以适应浮点格式。如果尾数四舍五入,则可能会增加指数。
  • 指数是否小于正常范围?如果是,则报告下溢。

因此,两个不同的浮点实现可能会针对相同的计算返回不同的下溢报告。

(关于处理下溢的一些额外规则。上述情况会导致触发下溢异常。但是,如果未启用此异常的陷阱并且结果是精确的[舍入没有改变任何内容],则会忽略“下溢”,并且下溢标志不会被引发。如果结果不精确,则会引发下溢并触发不精确异常。)


1

0
template <typename T>
class Real
{
  public:
    Real(T x) : x_(x) { }
    Real& operator/=(T rhs)
    {
        if (x_)
        {
            x_ /= rhs;
            if (!x_)
                do whatever you want for underflow...
        }
    }
    friend Real operator/(Real lhs, Real rhs)
        { return lhs /= rhs; }
    // similar for -= etc.
  private:
    T x_;
}

0

首先,您需要在构建中启用浮点异常的生成。其次,您必须在代码中捕获它们。

我做了类似于这样的事情(使用Visual Studio)

    void translateFPException( unsigned int u, EXCEPTION_POINTERS* pExp )
    {
    unsigned int fpuStatus = _clearfp(); // clear the exception 
    switch (u)
    {
    case STATUS_FLOAT_DENORMAL_OPERAND: 
        throw fe_denormal_operand(fpuStatus);
    case STATUS_FLOAT_DIVIDE_BY_ZERO: 
        throw fe_divide_by_zero(fpuStatus);
    case STATUS_FLOAT_INEXACT_RESULT: 
        throw fe_inexact_result(fpuStatus); 
    case STATUS_FLOAT_INVALID_OPERATION: 
        throw fe_invalid_operation(fpuStatus);  
    case STATUS_FLOAT_OVERFLOW: 
        throw fe_overflow(fpuStatus);   
    case STATUS_FLOAT_UNDERFLOW: 
        throw fe_underflow(fpuStatus);  
    case STATUS_FLOAT_STACK_CHECK: 
        throw fe_stack_check(fpuStatus);    
    default:
        throw float_exception(fpuStatus);
    };
    }

void initializeFloatingPointExceptionHandling()
{
    unsigned int fpControlWord = 0x00;

    _clearfp(); // always call _clearfp before enabling/unmasking a FPU exception
    // enabling an exception is done by clearing the respective bit in the control word
    errno_t success = _controlfp_s( &fpControlWord, 
                                    0xffffffff^( _EM_INVALID 
                                               | _EM_ZERODIVIDE 
                                               | _EM_OVERFLOW
                                               | _EM_DENORMAL
                                               | _EM_UNDERFLOW
                                               | _EM_INEXACT), _MCW_EM );
    if (success != 0)
    {
        stringstream errStream;
        errStream << success << " " << __FILE__ << ":" << __LINE__ << std::endl;
        throw exception(errStream.str().c_str());
    }

    _se_translator_function old =_set_se_translator(translateFPException);
}
void enableAllFPUExceptions()
{
    unsigned int oldMask = 0x00000000;

    _clearfp();
    // enabling is done by clearing the respective bit in the mask
    _controlfp_s(&oldMask, 0x00, _MCW_EM);
}

void disableAllFPUExceptions()
{
    unsigned int oldMask = 0x00000000;

    _clearfp();
    // disabling is done by setting the respective bit in the mask
    _controlfp_s(&oldMask, 0xffffffff, _MCW_EM);
}

然后你需要编写自己的异常(这只是一个摘录,但你应该能够理解概念)

    class float_exception : public exception
{
public:
    float_exception(unsigned int fpuStatus);

    unsigned int getStatus();
protected:
    unsigned int fpuStatus;
};

class fe_denormal_operand : public float_exception
{
public:
    fe_denormal_operand(unsigned int fpuStatus);
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接