Excepciones de coma flotante

Este tema proporciona información sobre las excepciones de coma flotante y cómo los programas pueden detectarlas y manejarlas.

El Instituto de Ingenieros Eléctricos y Electrónicos (IEEE) define un estándar para las excepciones de coma flotante denominado estándar IEEE para la aritmética de coma flotante binaria (IEEE 754). Este estándar define cinco tipos de excepción de coma flotante que deben señalarse cuando se detectan:

  • Operación no válida
  • división por cero
  • Desbordamiento
  • Subdesbordamiento
  • Cálculo inexacto

Cuando se produce una de estas excepciones en un proceso de usuario, se indica estableciendo un distintivo o tomando una condición de excepción. De forma predeterminada, el sistema establece un distintivo de estado en los registros de estado y control de coma flotante (FPSCR), lo que indica que se ha producido la excepción. Una vez que los distintivos de estado se establecen mediante una excepción, sólo se borran cuando el proceso los borra explícitamente o cuando el proceso finaliza. El sistema operativo proporciona subrutinas para consultar, establecer o borrar estos distintivos.

El sistema también puede hacer que se emita la señal de excepción de coma flotante (SIGFPE) si se produce una excepción de coma flotante. Debido a que este no es el comportamiento predeterminado, el sistema operativo proporciona subrutinas para cambiar el estado del proceso para que la señal esté habilitada. Cuando una excepción de coma flotante emite la señal SIGFPE , el proceso termina y genera un archivo principal si no hay ninguna subrutina de manejador de señales en el proceso. De lo contrario, el proceso llama a la subrutina del manejador de señales.

Subrutinas de excepción de coma flotante

Las subrutinas de excepción de coma flotante se pueden utilizar para:

  • Cambiar el estado de ejecución del proceso
  • Habilitar la señalización de excepciones
  • Inhabilitar excepciones o borrar distintivos
  • Determinar qué excepciones han causado la señal
  • Probar los distintivos de permanencia en memoria de excepción

Se proporcionan las subrutinas siguientes para realizar estas tareas:

Subrutina Tarea
fp_any_xcp o fp_divbyzero Probar los distintivos de permanencia en memoria de excepción
fp_enable o fp_enable_all Habilitar la señalización de excepciones
fp_inexact, fp_invalid_op, fp_iop_convert, fp_iop_infdinf, fp_iop_infmzr, fp_iop_infsinf, fp_iop_invcmp, fp_iop_snan, fp_iop_sqrt, fp_iop_vxsoft, fp_iop_zrdzr, o fp_overflow Probar los distintivos de permanencia en memoria de excepción
fp_sh_info Determina qué excepciones han causado la señal
estación_conjunto_sh_fp Inhabilita excepciones o borra distintivos
detección_fp Cambia el estado de ejecución del proceso
fp_underflow Prueba los distintivos de permanencia en memoria de excepción
acción de firma Instala el manejador de señales

Operación de manejador de condiciones de excepción de coma flotante

Para generar una condición de excepción, un programa debe cambiar el estado de ejecución del proceso utilizando la subrutina fp_trap y permitir que la excepción se atrape utilizando la subrutina fp_enable o fp_enable_all .

Cambiar el estado de ejecución del programa puede ralentizar el rendimiento porque la captura de coma flotante hace que el proceso se ejecute en modalidad serie.

Cuando se produce una condición de excepción de coma flotante, se emite la señal SIGFPE . De forma predeterminada, la señal SIGFPE hace que el proceso termine y produzca un archivo principal. Para cambiar este comportamiento, el programa debe establecer un manejador de señales para esta señal. Consulte sigaction, sigvec, o las subrutinas signal para obtener más información sobre los manejadores de señales.

Excepciones: comparación inhabilitada y habilitada

Consulte las listas siguientes para ver una ilustración de las diferencias entre los estados de proceso inhabilitado y habilitado y las subrutinas que se utilizan.

Excepciones-modelo inhabilitado

Las subrutinas siguientes prueban distintivos de excepción en el estado de proceso inhabilitado:

  • fp_any_xcp
  • distintivo_clr_fp
  • fp_divbyzero
  • fp_inexacto
  • operación_invalidación_fp
  • 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
  • desbordamiento de fp
  • fp_underflow

Modelo habilitado para excepciones

Las siguientes subrutinas funcionan en el estado de proceso habilitado:

Subrutina Estado de proceso
fp_enable o fp_enable_all Habilitar la señalización de excepciones
fp_sh_info Determina qué excepciones han causado la señal
estación_conjunto_sh_fp Inhabilita excepciones o borra distintivos
detección_fp Cambia el estado de ejecución del proceso
sigaction Instala el manejador de señales

Modalidades de captura imprecisas

Algunos sistemas tienen modalidades de captura imprecisas. Esto significa que el hardware puede detectar una excepción de coma flotante y entregar una interrupción, pero el proceso puede continuar mientras se entrega la señal. Como resultado, el registro de dirección de instrucción (IAR) está en una instrucción diferente cuando se entrega la interrupción.

Las modalidades de captura imprecisas provocan menos degradación del rendimiento que la modalidad de captura precisa. Sin embargo, algunas operaciones de recuperación no son posibles, porque la operación que ha causado la excepción no se puede determinar o porque la instrucción posterior puede haber modificado el argumento que ha causado la excepción.

Para utilizar excepciones imprecisas, un manejador de señales debe ser capaz de determinar si una condición de excepción era precisa o imprecisa.

Condiciones de excepción precisas

En una condición de excepción precisa, el registro de dirección de instrucción (IAR) apunta a la instrucción que ha causado la condición de excepción. Un programa puede modificar los argumentos de la instrucción y reiniciarla, o arreglar el resultado de la operación y continuar con la siguiente instrucción. Para continuar, el IAR debe incrementarse para que apunte a la siguiente instrucción.

Interrupciones imprecisas

En una trampa imprecisa, el IAR apunta a una instrucción más allá de la que causó la excepción. La instrucción a la que apunta el IAR no se ha iniciado. Para continuar la ejecución, el manejador de señales no incrementa el IAR.

Para eliminar la ambigüedad, eltrap_modese proporciona en la estructura fp_sh_info . Este campo especifica la modalidad de captura en vigor en el proceso de usuario cuando se ha especificado el manejador de señales. Esta información también se puede determinar examinando el registro de estado de máquina (MSR) en la estructura mstsave .

La subrutina fp_sh_info permite a un manejador de señales de coma flotante determinar si la excepción de coma flotante era precisa o imprecisa.

Nota: Incluso cuando la modalidad de captura precisa está habilitada, algunas excepciones de coma flotante pueden ser imprecisas (como las operaciones implementadas en el software). Del mismo modo, en el modo de captura imprecisa algunas excepciones pueden ser precisas.

Cuando se utilizan excepciones imprecisas, algunas partes del código pueden requerir que se notifiquen todas las excepciones de coma flotante antes de continuar. Para ello, se proporciona la subrutina fp_flush_imprecise . También se recomienda utilizar la subrutina atexit para registrar la subrutina fp_flush_imprecise para que se ejecute en la salida del programa. La ejecución en la salida garantiza que el programa no salga con excepciones imprecisas no notificadas.

Subrutinas específicas de hardware

Algunos sistemas tienen instrucciones de hardware para calcular la raíz cuadrada de un número de coma flotante y para convertir un número de coma flotante en un entero. Los modelos que no tienen estas instrucciones de hardware utilizan subrutinas de software para hacerlo. Cualquiera de los métodos puede provocar una condición de excepción si la excepción de operación no válida está habilitada. Las subrutinas de software informan, a través de la subrutina fp_sh_info , de que se ha producido una excepción imprecisa, porque el IAR no apunta a una única instrucción que se pueda reiniciar para reintentar la operación.

Ejemplo de un manejador de condiciones de excepción de coma flotante

/*
 * 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);
  }