繁体   English   中英

在C ++中处理浮点异常

[英]Handling Floating-Point exceptions in C++

我发现浮点模型/错误问题相当令人困惑。 这是一个我不熟悉的领域,我不是一个低级别的C / asm程序员,所以我很感激一点建议。

我有一个使用VS2012(VC11)构建的大型C ++应用程序,我已经将其配置为抛出浮点异常(或者更确切地说,允许C ++运行时和/或硬件抛出fp异常) - 并且它投入了很多它们在发布(优化)版本中,但不在调试版本中。 我假设这是由于优化和浮点模型(尽管为发布和调试版本设置了编译器/ fp:精确切换)。

我的第一个问题涉及管理应用程序的调试。 我想控制抛出fp异常的位置以及它们被“屏蔽”的位置。 这是必需的,因为我正在调试(优化的)发布版本(这是fp异常发生的地方) - 我想在我检测到问题的某些函数中禁用fp-exceptions,这样我就可以找到新的FP问题了。 但我对使用_controlfp_s执行此操作(工作正常)和编译器(和#pragma float_control)切换“/ fp:except”(这似乎没有任何效果)之间的区别感到困惑。 这两种机制有什么区别? 它们是否应该对fp异常产生相同的影响?

其次,我得到了一些“浮点堆栈检查”异常 - 包括一个似乎在调用GDI + dll时抛出的异常。 在网上搜索,这个异常的少数提及似乎表明它是由编译器错误引起的。 通常情况如此吗? 如果是这样,我该如何解决这个问题呢? 是否最好禁用问题函数的编译器优化,或者如果似乎没有返回任何错误的浮点值,则仅针对有问题的代码区域禁用fp-exceptions? 例如,在抛出此异常的GDI +调用(到GraphicsPath :: GetPointCount)中,实际返回的整数值似乎是正确的。 目前我正在使用_controlfp_s在GDI +调用之前立即禁用fp-exceptions - 然后再次使用它在调用后直接重新启用异常。

最后,我的应用程序确实进行了大量的浮点计算,并且需要稳健可靠,但不一定非常准确。 应用程序的本质是浮点值通常表示概率,因此本质上有些不精确。 但是,我想捕获任何纯逻辑错误,例如除以零。 什么是最好的fp模型? 目前我是:

  • 使用_controlfp_s和SIGFPE信号处理程序捕获所有fp异常(即EM_OVERFLOW | EM_UNDERFLOW | EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID),
  • 已启用非正规零(DAZ)和齐射到零(FTZ)(即_MM_SET_FLUSH_ZERO_MODE(_MM_DENORMALS_ZERO_ON)),以及
  • 我使用默认的VC11编译器设置/ fp:精确的/ fp:除了没有指定。

这是最好的型号吗?

感谢致敬!

大部分以下信息来自Bruce Dawson关于该主题的博文( 链接 )。

由于您正在使用C ++,因此可以创建一个RAII类,以范围方式启用或禁用浮点异常。 这使您可以更好地控制,以便只将异常状态暴露给代码,而不是自己手动管理调用_controlfp_s()。 此外,以这种方式设置的浮点异常状态是系统范围的,因此建议记住控制字的先前状态并在需要时恢复它。 RAII可以为您解决这个问题,对于您正在描述的GDI +问题是一个很好的解决方案。

异常标志_EM_OVERFLOW,_EM_ZERODIVIDE和_EM_INVALID是最重要的考虑因素。 当正或负无穷大是计算结果时会引发_EM_OVERFLOW,而当结果是信号NaN时会引发_EM_INVALID。 _EM_UNDERFLOW可以安全忽略; 它会在您的计算结果为非零时以及-FLT_MIN和FLT_MIN之间发出信号(换句话说,当您生成非正规时)。 由于浮点运算的性质,_EM_INEXACT过于频繁地引发任何实际用途,尽管如果在某些情况下尝试追踪不精确的结果,它可以提供信息。

SIMD代码为混音添加了更多皱纹; 因为你没有明确指出使用SIMD,所以我会省略对它的讨论,除非注意指定/ fp:fast以外的任何东西可以禁用VS 2012中代码的自动矢量化; 有关详细信息,请参阅此答案

我对前两个问题帮不了多少,但我有关于屏蔽FPU异常的问题的经验和建议。

我找到了这些功能

_statusfp()  (x64 and Win32)
_statusfp2() (Win32 only)
_fpreset()
_controlfp_s()
_clearfp()
_matherr()

在调试FPU异常和提供稳定,快速的产品时非常有用。

在调试时,我有选择地取消屏蔽异常,以帮助隔离在计算中生成fpu异常的代码行,在这种情况下,我无法避免调用其他不可预测的生成fpu异常的代码(比如.NET JIT除以零)。

在已发布的产品中,我使用它们来提供一个稳定的程序,该程序可以容忍严重的浮点异常,检测它们何时发生,并优雅地恢复。

当我必须调用无法更改的代码,没有可靠的异常处理,偶尔生成FPU异常时,我屏蔽所有FPU异常。

例:

#define BAD_FPU_EX (_EM_OVERFLOW | _EM_ZERODIVIDE | _EM_INVALID)
#define COMMON_FPU_EX (_EM_INEXACT | _EM_UNDERFLOW | _EM_DENORMAL)
#define ALL_FPU_EX (BAD_FPU_EX | COMMON_FPU_EX)

发行代码:

_fpreset();
Use _controlfp_s() to mask ALL_FPU_EX 
_clearfp();
... calculation
unsigned int bad_fpu_ex = (BAD_FPU_EX  & _statusfp());
_clearfp(); // to prevent reacting to existing status flags again
if ( 0 != bad_fpu_ex )
{
  ... use fallback calculation
  ... discard result and return error code
  ... throw exception with useful information
}

调试代码:

_fpreset();
_clearfp();
Use _controlfp_s() to mask COMMON_FPU_EX and unmask BAD_FPU_EX 
... calculation
  "crash" in debugger on the line of code that is generating the "bad" exception.

根据您的编译器选项,发布版本可能正在使用对FPU操作的内部调用,而调试版本可能会调用数学库函数。 对于诸如sqrt(-1.0)之类的无效操作,这两种方法可能具有显着不同的错误处理行为。

使用在64位Windows 7上使用VS2010构建的可执行文件,在Win32和x64平台上使用相同的代码时,我生成了略微不同的双精度算术值。 即使使用/ fp :: precise的非优化调试版本,fpu精度控制也明确设置为_PC_53,并且fpu舍入控制显式设置为_RC_NEAR。 我不得不调整一些比较双精度值的回归测试来考虑平台。 我不知道这是否仍然是VS2012的一个问题,但是抬起头来。

我一直在努力获得有关在linux上处理浮点异常的一些信息,我可以告诉你我学到了什么:有几种方法可以启用异常机制:

  1. fesetenv(FE_NOMASK_ENV); 启用所有例外
  2. feenableexcept(FE_ALL_EXCEPT);
  fpu_control_t fw; _FPU_GETCW(fw); fw |=FE_ALL_EXCEPT; _FPU_SETCW(fw); 

4。

> fenv_t envp; include bits/fenv.h   
> fegetenv(&envp);    
 envp.__control_word |= ~_FPU_MASK_OM;   
> fesetenv(&envp);

5。

> fpu_control_t cw;
> __asm__ ("fnstcw %0" : "=m" (*&cw));get config word
>cw |= ~FE_UNDERFLOW;
> __asm__ ("fldcw %0" : : "m" (*&cw));write config word

6.C ++模式:std :: feclearexcept(FE_ALL_EXCEPT);

有一些有用的链接: http: //frs.web.cern.ch/frs/Source/MAC_headers/fpu_control.h http://en.cppreference.com/w/cpp/numeric/fenv/fetestexcept http:// technopark02.blogspot.ro/2005/10/handling-sigfpe.html

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM