浮点异常

本主题提供了关于浮点异常以及程序如何检测和处理异常的信息。

电气电子工程师学会 (IEEE) 为浮点异常定义了称为二进制浮点算法 IEEE 标准 (IEEE 754) 的标准。 此标准定义了五种类型在检测到时必须发出信号的浮点异常:

  • 无效操作
  • 被零除
  • 溢出
  • 下溢
  • 不精确计算

用户进程中发生这些异常之一时,通过设置标记或采取陷阱发出信号。 缺省情况下,系统在“浮点状态和控制”寄存器 (FPSCR) 中设置一个状态标记,指示已经产生异常。 一旦由异常设置了状态标记,仅在进程明确清除标记或进程结束时才清除标记。 操作系统提供子例程来查询、设置或清除这些标记。

如果发生浮点异常,系统也可以导致发出浮点异常信号 (SIGFPE)。 因为这不是缺省行为,操作系统提供子例程来更改进程的状态以启用信号。 浮点异常发出 SIGFPE 信号时,如果进程中没有任何信号处理程序,进程终止并生成一个核心文件。 否则,进程调用信号处理程序子例程。

浮点异常子例程

浮点异常子例程可用于:

  • 更改进程的执行状态
  • 使得异常发出信号
  • 禁用异常或清除标记
  • 确定哪些异常引起信号
  • 测试异常粘滞标记

提供下列子例程来完成这些任务:

子例程 任务
fp_any_xcpfp_divbyzero 测试异常粘滞标记
fp_enablefp_enable_all 使得异常发出信号
fp_inexactfp_invalid_opfp_iop_convertfp_iop_infdinffp_iop_infmzrfp_iop_infsinf fp_iop_invcmpfp_iop_snanfp_iop_sqrtfp_iop_vxsoft fp_iop_zrdzrfp_overflow 测试异常粘滞标记
fp_sh_info 确定哪些异常引起信号
fp_sh_set_stat 禁用异常或清除标记
fp_trap 更改进程的执行状态
fp_underflow 测试异常粘滞标记
签名 安装信号处理程序

浮点陷阱处理程序操作

要生成陷阱,程序必须使用 fp_trap 子例程更改进程的执行状态,并使用 fp_enablefp_enable_all 子例程启用要捕获的异常。

更改程序的执行状态可能会由于浮点捕获导致进程以串行方式执行而降低性能。

发生浮点捕获时,发出 SIGFPE 信号。 缺省情况下,SIGFPE 信号导致进程终止并生成核心文件。 要更改此行为,程序必须为此信号建立一个信号处理程序。 请参阅 sigactionsigvec。 或 signal 子例程以获取有关信号处理程序的更多信息。

异常:禁用和启用比较

参阅下列列表以了解禁用和启用的进程状态之间的差异说明以及使用的子例程。

禁用异常的模型

下列子例程测试禁用的进程状态中的异常标记:

  • fp_any_xcp
  • fp_clr_flag
  • fp_divbyzero
  • fp_inexact
  • fp_invalid_op
  • fp_iop_convert
  • fp_iop_infdinf
  • fp_iop_infmzr
  • fp_iop_infsi
  • fp_iop_invcmp
  • fp_iop_snan
  • fp_iop_sqrt
  • fp_iop_vxsoft
  • fp_iop_zrdzr
  • fp_overflow
  • fp_underflow

启用异常的模型

下列子例程在启用的进程状态中运行:

子例程 处理状态
fp_enablefp_enable_all 使得异常发出信号
fp_sh_info 确定哪些异常引起信号
fp_sh_set_stat 禁用异常或清除标记
fp_trap 更改进程的执行状态
sigaction 安装信号处理程序

不精确捕获方式

一些系统具有不精确捕获方式。 这意味着硬件可以检测到浮点异常并传递中断,但是处理在传递信号期间可以继续。 结果是,指令地址寄存器 (IAR) 在传递中断时位于不同的指令处。

不精确捕获方式比精确捕获方式引起更少的性能降低。 然而,一些恢复操作不可能,因为无法确定引起异常的操作,或者因为随后的指令已经修改引起异常的参数。

要使用不精确的异常,信号处理程序必须能够确定陷阱精确还是不精确。

精确陷阱

在精确陷阱中,指令地址寄存器 (IAR) 指向引起陷阱的指令。 程序可以修改指令的参数并重新启动指令,或者确定操作的结果并继续下一条指令。 若要继续,必须递增 IAR 以指向下一条指令。

不精确陷阱

在不精确陷阱中,IAR 指向一条超过引起异常的指令的指令。 IAR 指向其的指令尚未启动。 要继续执行,信号处理程序不递增 IAR。

为消除模糊性,trap_mode字段在 fp_sh_info 结构中提供。 此字段指定输入信号处理程序时用户进程中生效的陷阱方式。 也可以通过检查 mstsave 结构中的机器状态寄存器 (MSR) 确定此信息。

fp_sh_info 子例程允许浮点信号处理程序确定浮点异常是精确的还是不精确的。

注意:即使启用了精确陷阱方式,一些浮点异常可能仍然不精确(例如软件中执行的操作)。 同样,在不精确陷阱方式中,一些异常可能精确。

使用不精确异常时,您代码的一些部分可能需要在进行之前报告所有浮点异常。 提供了 fp_flush_imprecise 子例程来完成此操作。 还建议使用 atexit 子例程来注册要在程序出口运行的 fp_flush_imprecise 子例程。 退出时运行确保程序不会以未报告的不精确异常退出。

特定于硬件的子例程

一些系统具有硬件指令来计算浮点数的平方根并将浮点数转换为整数。 没有这些硬件指令的模型使用软件子例程进行此操作。 如果启用了无效的操作异常,任何一种方法都可以引起陷阱。 软件子例程通过 fp_sh_info 子例程报告发生不精确的异常,因为 IAR 不指向可以重新启动来重试操作的单个指令。

浮点陷阱处理程序的示例

/*
 * This code demonstates a working floating-point exception
 * trap handler. The handler simply identifies which
 * floating-point exceptions caused the trap and return.
 * The handler will return the default signal return
 * mechanism longjmp().
 */
#include <signal.h>
#include <setjmp.h>
#include <fpxcp.h>
#include <fptrap.h>
#include <stdlib.h>
#include <stdio.h>
#define EXIT_BAD  -1
#define EXIT_GOOD  0
/*
 * Handshaking variable with the signal handler. If zero,
 * then the signal hander returns via  the default signal
 * return mechanism; if non-zero, then the signal handler
 * returns via longjmp.
 */
static int fpsigexit;
#define SIGRETURN_EXIT 0
#define LONGJUMP_EXIT  1
static jmp_buf jump_buffer;      /* jump buffer */
#define JMP_DEFINED 0            /* setjmp rc on initial call */
#define JMP_FPE     2            /* setjmp rc on return from */
                                 /* signal handler */
/*
 * The fp_list structure allows text descriptions
 * of each possible trap type to be tied to the mask
 * that identifies it.
 */
typedef struct
  {
  fpflag_t mask;
  char     *text;
  } fp_list_t;
/* IEEE required trap types */
fp_list_t
trap_list[] =
  {
      { FP_INVALID,      "FP_INVALID"},
      { FP_OVERFLOW,     "FP_OVERFLOW"},
      { FP_UNDERFLOW,    "FP_UNDERFLOW"},
      { FP_DIV_BY_ZERO,  "FP_DIV_BY_ZERO"},
      { FP_INEXACT,      "FP_INEXACT"}
  };
/* INEXACT detail list -- this is an system extension */
fp_list_t
detail_list[] =
  {
      { FP_INV_SNAN,   "FP_INV_SNAN" } ,
      { FP_INV_ISI,    "FP_INV_ISI" } ,
      { FP_INV_IDI,    "FP_INV_IDI" } ,
      { FP_INV_ZDZ,    "FP_INV_ZDZ" } ,
      { FP_INV_IMZ,    "FP_INV_IMZ" } ,
      { FP_INV_CMP,    "FP_INV_CMP" } ,
      { FP_INV_SQRT,   "FP_INV_SQRT" } ,
      { FP_INV_CVI,    "FP_INV_CVI" } ,
      { FP_INV_VXSOFT, "FP_INV_VXSOFT" }
  };
/*
 * the TEST_IT macro is used in main() to raise
 * an exception.
 */
#define TEST_IT(WHAT, RAISE_ARG)        \
  {                                     \
  puts(strcat("testing: ", WHAT));      \
  fp_clr_flag(FP_ALL_XCP);              \
  fp_raise_xcp(RAISE_ARG);              \
  }
/*
 * NAME: my_div
 *
 * FUNCTION:  Perform floating-point division.
 *
 */
double
my_div(double x, double y)
  {
  return x / y;
  }
/*
 * NAME: sigfpe_handler
 *
 * FUNCTION: A trap handler that is entered when
 *           a floating-point exception occurs. The
 *           function determines what exceptions caused
 *           the trap, prints this to stdout, and returns
 *           to the process which caused the trap.
 *
 * NOTES:    This trap handler can return either via the
 *           default return mechanism or via longjmp().
 *           The global variable fpsigexit determines which.
 *
 *           When entered, all floating-point traps are
 *           disabled.
 *
 *           This sample uses printf(). This should be used
 *           with caution since printf() of a floating-
 *           point number can cause a trap, and then
 *           another printf() of a floating-point number
 *           in the signal handler will corrupt the static
 *           buffer used for the conversion.
 *
 * OUTPUTS:  The type of exception that caused
 *           the trap.
 */
static void
sigfpe_handler(int sig,
               int code,
               struct sigcontext *SCP)
  {
  struct mstsave * state = &SCP->sc_jmpbuf.jmp_context;
  fp_sh_info_t flt_context;     /* structure for fp_sh_info()
                                /* call */
  int i;                        /* loop counter */
  extern int fpsigexit;         /* global handshaking variable */
  extern jmp_buf jump_buffer    /*  */

  /*
   * Determine which floating-point exceptions caused
   * the trap. fp_sh_info() is used to build the floating signal
   * handler info  structure, then the member
   * flt_context.trap can be examined. First the trap type is
   * compared for the IEEE required traps, and if the trap type
   * is an invalid operation, the detail bits are examined.
   */

  fp_sh_info(SCP, &flt_context, FP_SH_INFO_SIZE);
static void
sigfpe_handler(int sig,
               int code,
               struct sigcontext *SCP)
  {
  struct mstsave * state = &SCP->sc_jmpbuf.jmp_context;
  fp_sh_info_t flt_context;     /* structure for fp_sh_info()
                                /* call */
  int i;                        /* loop counter */
  extern int fpsigexit;         /* global handshaking variable */
  extern jmp_buf jump_buffer;   /*  */

  /*
   * Determine which floating-point exceptions caused
   * the trap. fp_sh_info() is used to build the floating signal
   * handler info  structure, then the member
   * flt_context.trap can be examined. First the trap type is
   * compared for the IEEE required traps, and if the trap type
   * is an invalid operation, the detail bits are examined.
   */

  fp_sh_info(SCP, &flt_context, FP_SH_INFO_SIZE);
  for (i = 0; i < (sizeof(trap_list) / sizeof(fp_list_t)); i++)
      {
      if (flt_context.trap & trap_list[i].mask)
        (void) printf("Trap caused by %s error\n", trap_list[i].text);
      }
  if (flt_context.trap & FP_INVALID)
      {
      for (i = 0; i < (sizeof(detail_list) / sizeof(fp_list_t)); i++)
          {
          if (flt_context.trap & detail_list[i].mask)
            (void) printf("Type of invalid op is %s\n", detail_list[i].text);
          }
      }
  /* report which trap mode was in effect */
  switch (flt_context.trap_mode)
      {
    case FP_TRAP_OFF:
      puts("Trapping Mode is OFF");
      break;

    case FP_TRAP_SYNC:
      puts("Trapping Mode is SYNC");
      break;

    case FP_TRAP_IMP:
      puts("Trapping Mode is IMP");
      break;

    case FP_TRAP_IMP_REC:
      puts("Trapping Mode is IMP_REC");
      break;

    default:
      puts("ERROR:  Invalid trap mode");
      }
  if (fpsigexit == LONGJUMP_EXIT)
      {
      /*
       * Return via longjmp. In this instance there is no need to
       * clear any exceptions or disable traps to prevent
       * recurrence of the exception, because on return the
       * process will have the signal handler's floating-point
       * state.
       */
      longjmp(jump_buffer, JMP_FPE);
      }
  else
      {
      /*
       * Return via default signal handler return mechanism.
       * In this case you must take some action to prevent
       * recurrence of the trap, either by clearing the
       * exception bit in the fpscr or by disabling the trap.
       * In this case, clear the exception bit.
       * The fp_sh_set_stat routine is used to clear
       * the exception bit.
       */
      fp_sh_set_stat(SCP, (flt_context.fpscr & ((fpstat_t) ~flt_context.trap)));
      
      /*
       * Increment the iar of the process that caused the trap,
       * to prevent re-execution of the instruction.
       * The FP_IAR_STAT bit in flt_context.flags indicates if
       * state->iar points to an instruction that has logically
       * started. If this bit is true, state->iar points to
       * an operation that has started and will cause another
       * exception if it runs again. In this case you want to
       * continue execution and ignore the results of that
       * operation, so the iar is advanced to point to the
       * next instruction. If the bit is false, the iar already
       * points to the next instruction that must run.
       */

      if ( flt_context.flags & FP_IAR_STAT )
          {
          puts("Increment IAR");
          state->iar += 4;
          }
      }
  return;
  }

/*
 * NAME: main
 *
 * FUNCTION: Demonstrate the sigfpe_handler trap handler.
 *
 */
int
main(void)
  {
  struct sigaction response;
  struct sigaction old_response;
  extern int fpsigexit;
  extern jmp_buf jump_buffer;
  int jump_rc;
  int trap_mode;
  double arg1, arg2, r;

  /*
   * Set up for floating-point trapping. Do the following:
   *  1.  Clear any existing floating-point exception flags.
   *  2.  Set up a SIGFPE signal handler.
   *  3.  Place the process in synchronous execution mode.
   *  4.  Enable all floating-point traps.
   */
  fp_clr_flag(FP_ALL_XCP);
  (void) sigaction(SIGFPE, NULL, &old_response);
  (void) sigemptyset(&response.sa_mask);
  response.sa_flags = FALSE;
  response.sa_handler = (void (*)(int)) sigfpe_handler;
  (void) sigaction(SIGFPE, &response, NULL);
  fp_enable_all();
  /*
   * Demonstate trap handler return via default signal handler
   * return. The TEST_IT macro will raise the floating-point
   * exception type given in its second argument. Testing
   * is done in this case with precise trapping, because
   * it is supported on all platforms to date.
   */
  trap_mode = fp_trap(FP_TRAP_SYNC);
  if ((trap_mode == FP_TRAP_ERROR) ||
      (trap_mode == FP_TRAP_UNIMPL))
      {
      printf("ERROR: rc from fp_trap is %d\n",
             trap_mode);
      exit(-1);
      }

  (void) printf("Default signal handler return: \n");
  fpsigexit = SIGRETURN_EXIT;
  TEST_IT("div by zero", FP_DIV_BY_ZERO);
  TEST_IT("overflow",    FP_OVERFLOW);
  TEST_IT("underflow",   FP_UNDERFLOW);
  TEST_IT("inexact",     FP_INEXACT);
  TEST_IT("signaling nan",      FP_INV_SNAN);
  TEST_IT("INF - INF",          FP_INV_ISI);
  TEST_IT("INF / INF",          FP_INV_IDI);
  TEST_IT("ZERO / ZERO",        FP_INV_ZDZ);
  TEST_IT("INF * ZERO",         FP_INV_IMZ);
  TEST_IT("invalid compare",    FP_INV_CMP);
  TEST_IT("invalid sqrt",       FP_INV_SQRT);
  TEST_IT("invalid coversion",  FP_INV_CVI);
  TEST_IT("software request",   FP_INV_VXSOFT);
  /*
   * Next, use fp_trap() to determine what the
   * the fastest trapmode available is on
   * this platform.
   */
  trap_mode = fp_trap(FP_TRAP_FASTMODE);
  switch (trap_mode)
      {
    case FP_TRAP_SYNC:
      puts("Fast mode for this platform is PRECISE");
      break;

    case FP_TRAP_OFF:
      puts("This platform dosn't support trapping");
      break;
    case FP_TRAP_IMP:
      puts("Fast mode for this platform is IMPRECISE");
      break;
    case FP_TRAP_IMP_REC:
      puts("Fast mode for this platform is IMPRECISE RECOVERABLE");
      break;
    default:
      printf("Unexpected return code from fp_trap(FP_TRAP_FASTMODE): %d\n",
             trap_mode);
      exit(-2);
      }
  /*
   * if this platform supports imprecise trapping, demonstate this.
   */

  trap_mode = fp_trap(FP_TRAP_IMP);
  if (trap_mode != FP_TRAP_UNIMPL)
      {
      puts("Demonsrate imprecise FP execeptions");
      arg1 = 1.2;
      arg2 = 0.0;
      r = my_div(arg1, arg2);
      fp_flush_imprecise();
      }
  /* demonstate trap handler return via longjmp().
   */

  (void) printf("longjmp return: \n");
  fpsigexit = LONGJUMP_EXIT;
  jump_rc = setjmp(jump_buffer);

  switch (jump_rc)
      {
    case JMP_DEFINED:
      (void) printf("setjmp has been set up; testing ...\n");
      TEST_IT("div by zero", FP_DIV_BY_ZERO);
      break;

    case JMP_FPE:
      (void) printf("back from signal handler\n");
      /*
       * Note that at this point the process has the floating-
       * point status inherited from the trap handler. If the
       * trap hander did not enable trapping (as the example
       * did not) then this process at this point has no traps
       * enabled.  We create a floating-point exception to
       * demonstrate that a trap does not occur, then re-enable
       * traps.
       */
      (void) printf("Creating overflow; should not trap\n");
      TEST_IT("Overflow", FP_OVERFLOW);
      fp_enable_all();
      break;

    default:
      (void) printf("unexpected rc from setjmp: %d\n", jump_rc);
      exit(EXIT_BAD);
      }
  exit(EXIT_GOOD);
  }