【问题标题】:How do I use try...catch to catch floating point errors?如何使用 try...catch 捕获浮点错误?
【发布时间】:2011-02-15 17:20:59
【问题描述】:

我在 Visual Studio Express 中使用 C++ 生成随机表达式树,用于遗传算法类型的程序。

因为它们是随机的,所以树经常生成:除以零、上溢、下溢以及返回“inf”和其他字符串。我可以为字符串编写处理程序,但文献让我对其他人感到困惑。如果我理解正确,我必须先设置一些标志?

建议和/或一些文献的指针将不胜感激。 编辑:双变量中返回的值是 1.#INF 或 -1.#IND。我把它们称为字符串是错误的。

【问题讨论】:

  • C++ 没有规定任何这些操作都应该引发异常。它们导致未定义的行为。 (这可能会崩溃,或者抛出异常,或者什么都不做,o ...)
  • 但是 C99 和 POSIX 确实指定了这些东西,并提供了一个数字异常接口。但是,如果 Peter 得到的是字符串而不是 FP 无穷大,那么他是否可以控制实际的数字还不太清楚。

标签: c++ error-handling


【解决方案1】:

根据 http://msdn.microsoft.com/en-us/library/aa289157%28v=vs.71%29.aspx#floapoint_topic8 ,
在 MSVC 中似乎可以抛出 C++ 异常

创建异常类:

class float_exception : public std::exception {};
class fe_denormal_operand : public float_exception {};
class fe_divide_by_zero : public float_exception {};
class fe_inexact_result : public float_exception {};
class fe_invalid_operation : public float_exception {};
class fe_overflow : public float_exception {};
class fe_stack_check : public float_exception {};
class fe_underflow : public float_exception {};

使用结构化异常处理程序将 C++ throw 映射到 FPU 异常

void se_fe_trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
    switch (u)
    {
    case STATUS_FLOAT_DENORMAL_OPERAND:   throw fe_denormal_operand();
    case STATUS_FLOAT_DIVIDE_BY_ZERO:     throw fe_divide_by_zero();
    etc...
    };
}
. . .
_set_se_translator(se_fe_trans_func);

然后你可以使用 try catch

try
{
    floating-point code that might throw divide-by-zero 
   or other floating-point exception
}
catch(fe_divide_by_zero)
{
    cout << "fe_divide_by_zero exception detected" << endl;
}
catch(float_exception)
{
    cout << "float_exception exception detected" << endl;
}

【讨论】:

  • 这应该是批准的答案:它对应于原始问题(接受的答案基本上说“你不应该打扰”,第二个说“你不能”(这是不正确的)。跨度>
【解决方案2】:

您确定要捕获它们而不是忽略它们吗?假设您只想忽略它们:

看到这个: http://msdn.microsoft.com/en-us/library/c9676k6h.aspx

对于_MCW_EM掩码,清除掩码设置异常,允许硬件异常;设置掩码隐藏异常。

所以你会想做这样的事情:

#include <float.h>
#pragma fenv_access (on)

void main()
{
    unsigned int fp_control_word;
    unsigned int new_fp_control_word;

    _controlfp_s(&fp_control_word, 0, 0);

    // Make the new fp env same as the old one,
    // except for the changes we're going to make
    new_fp_control_word = fp_control_word | _EM_INVALID | _EM_DENORMAL | _EM_ZERODIVIDE | _EM_OVERFLOW | _EM_UNDERFLOW | _EM_INEXACT;
    //Update the control word with our changes
    _controlfp_s(&fp_control_word, new_fp_control_word, _MCW_EM)


}

这里的一些混淆可能是对“例外”一词的使用。在 C++ 中,这通常是指语言内置的异常处理系统。浮点异常是完全不同的野兽。标准 FPU 需要支持的例外都在 IEEE-754 中定义。这些发生在浮点单元内部,根据浮点单元的控制标志的设置方式,它可以做不同的事情。通常会发生以下两种情况之一: 1) 异常被忽略,FPU 设置一个标志,指示其状态寄存器中发生错误。 2) FPU 不会忽略异常,因此会生成一个中断,并调用为浮点错误设置的任何中断处理程序。通常这对你有好处,比如让你在调试器中中断那行代码或生成核心文件。

您可以在此处找到有关 IEE-754 的更多信息:http://www.openwatcom.org/ftp/devel/docs/ieee-754.pdf

一些额外的浮点引用: http://docs.sun.com/source/806-3568/ncg_goldberg.html http://floating-point-gui.de/

【讨论】:

  • dthorpe:实际上,我并没有假设它在 x86 处理器上运行。 MSDN 文档明确指出 _controlfp_s 是执行此操作的新“平台无关”方式。我怀疑平台无关他们指的是 CPU 类型,因为显然并非所有 x86 操作系统都具有此功能 - 只要他在 Windows 平台上执行此操作,他应该没问题。
  • 另外请注意,我已经看到库在浮点控制字的设置上发生争执,每个库都将其偏好强加于另一个。几乎和打印机驱动程序在程序提要下更改区域设置一样糟糕......
  • @George:多么棒的答案!参考资料非常有趣。你是对的,我只是想忽略它们。我会检查答案是否在我可接受的范围内,如果不是,我会抛出我自己的异常。这些标志很高兴知道。谢谢。
  • @Peter Stewart:在回答问题时要牢记的一件重要事情是,如果结果是 NaN(不是数字),则比较运算符不会按照您认为的方式工作工作。例如,检测结果是否为 NaN 的一种简单方法是检查“f != f”是否返回 true。您可能也想检查一下。看到这个问题:stackoverflow.com/questions/570669/…
【解决方案3】:

C++ 运行时环境在这里一点帮助也没有。您必须自己在代码中明确执行这些检查。当然,除非您调用的函数正在执行这些检查——在这种情况下,这取决于它们在发生错误时的行为。

让我解释一下:

double divide(double a, double b) {
    return a / b;  // undefined if b is zero
}

实际上应该是

double divide(double a, double b) {
    if( b == 0 ) {
        // throw, return, flag, ...  you choose how to signal the error
    }
    return a / b;  // b can't possibly be zero here
}

如果在除以零时失败的代码不是您的,那么您必须深入挖掘以找出它在错误威胁的情况下会做什么。它会扔吗?立个flag?询问作者和/或阅读来源。

这是一个例外的例子:

struct bad_value : public std::exception { };

double divide(double a, double b) {
    if( b == 0 ) throw bad_value("Division by zero in divide()");
    return a / b;  // b can't possibly be zero here
}

// elsewhere (possibly in a parallel universe) ...

    try {
        double r1 = divide(5,4);
        double r2 = divide(5,0);
    } catch( bad_value e ) {
        // ...
    }

【讨论】:

  • 谢谢。看来我必须实现自己的“投掷”。
【解决方案4】:

从未尝试过,但假设除以零会引发异常,您可以像这样将代码包装在 try/catch 中:

试试 { // 电位除以零... } 抓住(...) { // ... 捕获所有异常 }

【讨论】:

  • 错了,因为 C++ 运行时环境在这里帮不了你。除了程序员特别要求抛出的原因之外,它不会因任何原因抛出异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-21
  • 2021-01-26
  • 2016-05-29
  • 2021-10-08
  • 2013-07-18
  • 1970-01-01
相关资源
最近更新 更多