C++ feholdexcept()

2024 年 8 月 29 日 | 阅读 6 分钟

计算机程序中的浮点运算常常涉及近似值,这可能导致不准确和异常情况。在执行敏感的数值计算时,这些异常可能导致程序意外终止或输出不正确。C++ 编程语言提供了处理这些浮点异常的机制,以及在需要时临时关闭异常处理的函数。

控制浮点异常的一个此类函数是feholdexcept()。此函数允许保存当前的浮点环境,清除标志以暂时关闭异常,并在代码执行后恢复环境。通过隔离可能产生虚假异常的代码段,可以使程序更健壮、更容错。

feholdexcept() 函数声明在 <fenv.h> 头文件中,它允许保存和清除由其 fenv_t 环境参数指定的浮点异常标志。在禁用了异常的情况下执行完风险代码后,可以使用 fenv_setenv() 恢复以前的环境。本文将解释 feholdexcept() 函数,演示其用法,以及它如何帮助管理数值异常。

通过理解 feholdexcept() 等函数,开发人员可以在 C++ 程序中隔离不稳定的数值运算,同时保持其他部分的异常处理行为。它有助于编写正确、容错的代码,避免因不可避免的浮点不准确性而导致的意外终止。

什么是 feholdexcept 函数 ()

feholdexcept() 函数可以保存和清除当前的浮点环境,以暂时关闭浮点异常。它在 C++ 语言中的声明是:

fenv_t feholdexcept(fenv_t env);

它接受一个参数 env,一个 fenv_t 对象,用于存储现有状态。fenv_t 是在 <fenv.h> 中定义的类型,表示整个浮点环境。

在 feholdexcept() 内部,当前的异常标志会被保存到 env 参数中。这些标志指示何时引发了浮点异常,例如无效操作、除以零、溢出或其他数值错误。

可用的浮点异常包括:

  • FE_DIVBYZERO - 除以零
  • FE_INEXACT - 不精确结果
  • FE_INVALID - 无效操作
  • FE_OVERFLOW - 数值溢出
  • FE_UNDERFLOW - 数值下溢

在存储了现有标志之后,feholdexcept() 会清除传递给它的指定异常标志。通常,会传递 FE_ALL_EXCEPT 来一次性清除所有标志。

在清除了标志之后,受保护段内的任何代码都不会引发异常,即使是数学上错误的运算。存储在 env 中的先前环境状态稍后可以通过调用 fenv_setenv() 来恢复,该函数会重新设置标志。这可以隔离需要宽松处理的代码段,同时在其他地方保持严格的行为。

总而言之,feholdexcept() 允许在特定代码块中关闭异常抛出,从而控制 C++ 程序中数值异常的处理位置。它有助于更好地管理浮点行为。

feholdexcept() 函数的用途

  • 在敏感的数值代码中临时关闭浮点异常:在可能因不可避免的精度问题而产生异常的代码段之前调用 feholdexcept()。这可以防止程序意外终止。
  • 隔离有 bug 的浮点代码:在您怀疑存在数值 bug 的代码周围使用 feholdexcept()/fenv_setenv() 来允许执行。稍后纠正时,将再次为该代码启用异常。
  • 提高计算性能:异常处理会降低数学密集型程序的运行速度。feholdexcept() 可以有效地关闭非关键部分中的异常处理,以提高速度。
  • 让迭代计算继续进行:某些算法可能会偶尔遇到异常,但会产生可用的中间结果。将迭代包装在 feholdexcept() 中可以允许计算完成,而不是过早终止。
  • 保持周围的异常行为:通过仅在本地清除标志,feholdexcept() 保护段外部的代码将继续正常运行,并在适当的时候抛出异常。
  • 实现自定义浮点异常处理:在自定义异常处理代码段周围保存和恢复环境,而不是使用 C++ 的默认机制。
  • 逐步启用异常:在迁移或测试浮点代码时,暂时使用 feholdexcept(),然后“缩小”其范围以逐步修复和暴露问题。
  • 降低调试复杂度:在调试期间禁用某些代码中的异常,以便独立地简化已知数值问题部分的分析。

feholdexcept() 函数的实现

让我们来看一个 C++ 程序来演示 feholdexcept() 函数

输出

Result of division: inf
No exceptions raised during division.

说明

  1. 包含必要的头文件
    1. iostream: 用于输入/输出流功能
    2. cfenv: 用于浮点环境函数,例如 feholdexcept()
    3. cmath: 包含各种数学函数。
  2. 定义一个 safeDivision() 函数,它接受两个 double 类型参数(被除数和除数),并返回它们的除法结果(double 类型)。
  3. 在 safeDivision() 内部:声明一个 fenv_t 对象 currentEnv,用于保存当前的浮点环境。b. 调用 fegetenv() 并传入 currentEnv 的地址。它会将当前的浮点状态保存到 currentEnv 中。
  4. 执行传入参数之间的实际除法运算,并将结果存储起来。
  5. 声明另一个 fenv_t 对象 newEnv。它将与 feholdexcept() 一起使用。
  6. 调用 feholdexcept(),传入 newEnv 的地址。它会将当前环境保存到 newEnv 中并清除标志。
  7. 使用 feclearexcept() 显式清除溢出和除以零标志。这样做是因为保存在 newEnv 中的先前环境可能因为之前的操作而设置了这些标志。我们希望在此函数内部有一个干净的环境。
  8. 返回除法结果。此时,由于环境已清除,除法期间的任何浮点异常都不会生成异常。
  9. 在 main() 函数中
  10. 使用 feclearexcept() 清除所有浮点异常标志。
  11. 定义被除数和除数值。将除数设置为 0 以引起除以零。
  12. 调用 safeDivision(),它会返回结果。
  13. 打印结果。
  14. 使用 fetestexcept() 检查 safeDivision() 期间是否引发了任何异常。f. 根据上一步打印一条适当的消息。

程序 2

让我们来看另一个 C++ 程序来演示 feholdexcept() 函数

输出

Exceptions raised: FE_DIVBYZERO
Exceptions raised: No exceptions found
Exceptions raised: FE_DIVBYZERO

说明

  1. 包含必要的头文件 - iostream 用于 I/O,cfenv 用于浮点环境函数
  2. 定义一个 displayExceptions() 函数,使用 fetestexcept() 打印任何当前引发的浮点异常
  3. 在 main() 函数中
  4. 声明一个 fenv_t 对象 current environment,用于保存环境
  5. 使用 feraiseexcept() 显式引发一个“除以零”异常,以模拟错误
  6. 第一次调用 displayExceptions() 来打印已引发的异常
  7. 声明另一个 fenv_t 对象 newEnvironment
  8. 调用 feholdexcept(),传入 newEnvironment 的地址。这将
    1. 将包含标志的当前环境保存到 newEnvironment 中
    2. 清除当前环境中的标志
  9. 调用 displayExceptions()。此时,由于第 8 步,它将不会显示任何已引发的异常。
  10. 使用 feupdateenv() 将 newEnvironment 中保存的环境恢复到当前环境中
  11. 第三次调用 displayExceptions(),此时会再次显示“FE_DIVBYZERO”异常,因为环境已恢复

总结

  • 引发了一个异常。
  • 使用 feholdexcept() 保存和清理环境
  • 之后,代码执行时没有引发异常。
  • 最后,通过指针恢复了原始环境,同时也恢复了异常。

这表明 feholdexcept() 可以在需要时临时禁用和恢复异常处理。

结论

feholdexcept() 函数为 C++ 开发人员在执行数值计算时提供了对浮点异常处理的精细控制。通过在保存环境后清除标志,可以在敏感或不稳定的计算期间局部禁用异常,使代码能够继续执行而不是突然终止。之后,可以使用 fenv_setenv() 恢复原始状态,以便程序的其余部分正常运行。这种方法可以隔离有问题的数学代码,并限制宽松异常的范围,同时在其他部分保持严格的行为。总之,feholdexcept() 及相关函数使用户能够更好地管理不可避免的浮点不准确性和异常。它们有助于编写正确、容错的数值代码,以处理偶尔的异常而不会中断。通过明智地利用这些环境控制机制,C++ 程序可以变得更加健壮,为当今复杂的计算挑战做好生产准备。