FPE: a floating-point exception handler for f90


The module fpe provides a set of simple calls for controlling code behaviour under floating-point exceptions.


Introduction.
Using the fpe module.
Acquiring the fpe module source.
Linking with the fpe module.
Portability issues.

The the fpe module API:

fpe_enable:
Enable interrupts for classes of exceptions.
fpe_disable:
Disable interrupts for classes of exceptions.
fpe_trap:
Set trap for classes of exceptions.

  1. Introduction

    Numbers on digital computers are represented as a series of bits, and thus intrinsically only deal with integers. A number model for "real" numbers is generally used to provide a representation of non-integers. Since only a finite number of bits is used, this representation does not actually map onto the real number set, but a finite set of those numbers admitting a terminating representation (i.e excluding irrational numbers and non-terminating fractions). This number model is now almost universally based on floating-point (FP) numbers, using a mantissa and an exponent. The IEEE has specified a standard FP number model, which is used on most, but not all, modern computers. When FP computations produce numbers that cannot be represented in the number model, an exception is said to have occurred. Exceptions fall into several classes, of which the most common include:

    Inexact result:
    FP result has no exact representation and must be rounded. This occurs extremely frequently: in fact the probability that an FP calculation has an exact result is in theory vanishingly small.
    Overflow:
    Absolute value of FP result is larger than the largest FP number representable. The F90 intrinsic HUGE() returns this value.
    Underflow:
    Absolute value of FP result is smaller than the smallest FP number representable. The F90 intrinsic TINY() returns this value.
    Divide by zero:
    FP division has a 0 in the denominator.
    Invalid operand:
    FP operation is attempting to treat a series of bits that is not a valid FP number. Invalid FP numbers are generally called NaNs (not-a-number). This exception class may also cover other invalid operands, such as a negative argument to a FP square-root operation.

    Computing units maintain a word in memory in order to track the occurrence of exceptions. Each bit in this word signals the occurrence of a different class of exceptions, such as those listed above. This word must be tested to know if an exception has occurred. The result of this test can be used to control subsequent program behaviour, Principally, we might wish to interrupt the program if certain classes of exceptions occur, though other more refined consequences may also be imagined.

    There is a computational burden associated with testing for exceptions, and also one with continuing the execution of a program after the computation has gone awry. This module provides a standard interface to a simple range of behaviour under exceptions. This include enabling and disabling interrupts for certain classes of exceptions, and a trap function that merely signals to the user that an exception has occurred.

  2. Using the fpe module

    The fpe module defines four classes of exceptions: FP_DIVIDE_BY_ZERO, FP_OPERAND_IS_NAN, FP_OVERFLOW, FP_UNDERFLOW. These are integers and may be summed to designate multiple classes.

    The calls fpe_enable and fpe_disable may be used to turn on FP error interrupts for different code sections. fpe_trap returns .TRUE. if an exception has occurred. Consider the test program:

    program fpetest
      use fpe
      implicit none
    
      real :: a, b
    
      a = 1.
      b = 1.
      print *
      print *, 'Divide by zero with interrupts disabled...'
      call fpe_disable(FP_DIVIDE_BY_ZERO)
      print *, 'fpe_trap(FP_DIVIDE_BY_ZERO)=', fpe_trap(FP_DIVIDE_BY_ZERO)
      print *, 'divide by zero=', a/(a-b)
      print *, 'fpe_trap(FP_DIVIDE_BY_ZERO)=', fpe_trap(FP_DIVIDE_BY_ZERO)
    
      print *
      print *, 'Divide by zero with interrupts enabled...'
      print *, 'The next line should be an abort message:'
      call fpe_enable(FP_DIVIDE_BY_ZERO)
      print *, 'divide by zero=', a/(a-b)
      
    end program fpetest
    

    This produces (on SGIs) the output:

     Divide by zero with interrupts disabled...
     fpe_trap(FP_DIVIDE_BY_ZERO)= F
     divide by zero= Infinity
     fpe_trap(FP_DIVIDE_BY_ZERO)= T
     
     Divide by zero with interrupts enabled...
     The next line should be an abort message:
    Floating Exception
    Abort
    

    This example provides a comprehensive test case for the FPE module.

    In the first computation of 1./(1.-1.), FPE interrupts have been disabled. Before the computation the FP_DIVIDE_BY_ZERO exception is shown as not having occurred. Subsequently this exception bit has been set to .TRUE. In the second instance, FPE interrupts have been enabled, and cause the program to abort.

  3. FPE module call syntax

    The public interfaces to the fpe module are described here in alphabetical order. The fpe module defines four classes of exceptions: FP_DIVIDE_BY_ZERO, FP_OPERAND_IS_NAN, FP_OVERFLOW, FP_UNDERFLOW, which are public integer parameters use-associated from the module. The argument flag to any of the routines below can be a sum of any of these. An absent flag argument is equivalent to all of the exceptions, i.e equivalent to flag=FP_DIVIDE_BY_ZERO+FP_OPERAND_IS_NAN+FP_OVERFLOW+FP_UNDERFLOW.

    1. fpe_disable

      subroutine fpe_disable(flag)
        integer, intent(in), optional :: flag
      

      Causes the program to continue execution even if one of the exceptions in flag has occurred, until a subsequent call to fpe_enable.

    2. fpe_enable

      subroutine fpe_enable(flag)
        integer, intent(in), optional :: flag
      

      Causes the program to abort if any of the exceptions in flag occurs. This involves frequent exception testing and may slow down the program. Measurements are needed to establish whether the slowdown is tolerable for the codes and computational platforms in question.

    3. fpe_trap

      logical function fpe_trap(flag)
        integer, intent(in), optional :: flag
      

      returns .TRUE. if any of the exceptions in flag has occurred. This may be an alternative to the computational burden of fpe_enable, since the user retains control of when to test for exceptions. However, the disadvantage is that the location of the original exception is lost.

  4. Acquiring fpe module source

    GFDL users can copy the file /net/vb/public/utils/fpe.F90. External users can download the source here. The current public version number is 2.2.

  5. Compiling and linking to fpe module

    Any module or program unit using the fpe module must contain the line

    use fpe
    

    The source file for the fpe module is fpe.F90.

    Compiling with the cpp flag test_fpe turned on:

    f90 -Dtest_fpe fpe.F90
    

    will produce a program that will exercise certain portions of the the fpe module module.

  6. Portability issues

    The fpe module was written specifically for the SGIs using the MIPSpro f90 compiler. They use the non-standard FTN_IEEE_DEFINITIONS module that comes with this compiler and provides exception handling functionality following the IEEE FP number model.

    This will shortly be extended to various compilers on the Linux platform. The module is deemed unnecessary on Crays, on which GFDL seems to have got by quite satisfactorily for many years without this degree of user control.

    Tim Yeager has written an excellent introduction to FPE behaviour for GFDL users, including various run-time and compile-time options for controlling this behaviour. Slides from his recent talk on the subject, and associated files, are available in /home/ty/doc/trap.talk.

  7. Changes

    The RCS log for fpe.F90 contains a comprehensive list of changes. In the unlikely event that you should wish to check out a retro version, please get in touch with me, Balaji.


Author: V. Balaji
Document last modified