Exceptions and Exception Handling |
4 |
![]() |
This chapter is organized into the following sections:
Table 4-1 condenses information found in IEEE Standard 754-1985. It describes the five floating-point exceptions and the default response of an IEEE arithmetic environment when these exceptions are raised.
Predicates | |||
math |
c, c++ |
f77 |
Invalid Exception if unordered |
= |
== |
.EQ. |
no |
|
!= |
.NE. |
no |
> |
> |
.GT. |
yes |
|
>= |
.GE. |
yes |
< |
< |
.LT. |
yes |
|
<= |
.LE. |
yes |
i = ieee_flags(action, mode, in, out);
A program can test, set, or clear the accrued exception status flags using the ieee_flags function by supplying the string ``exception'' as the second argument. For example, to clear the overflow exception flag from FORTRAN, write:
character*8 out call ieee_flags('clear', 'exception', 'overflow', out) |
To determine whether an exception has occurred from C or C++,
use:
i = ieee_flags("get", "exception", in, out); |
When the action is "get", the string returned in
out is:
character*8 out i = ieee_flags('get', 'exception', 'division', out) |
i = ieee_flags("get", "exception", "all", out); |
Besides returning the name of an exception in out,
ieee_flags returns an integer value that combines all of the
exception flags currently raised. This value is the bitwise
``or'' of all the accrued exception flags, where each flag is
represented by a single bit as shown in Table 4-3. The positions of
the bits corresponding to each exception are given by the
fp_exception_type values defined in the file
sys/ieeefp.h. (Note that these bit positions are
machine-dependent and need not be contiguous.)
This fragment of a C or C++ program shows one way to decode the return
value.
Locating an Exception
Often, programmers do not write programs with exceptions in mind, so
when an exception is detected, the first question asked is: Where did
the exception occur? Of course, one way to locate where an exception
occurs is to test the exception flags at various points throughout a
program, but to isolate an exception precisely by this approach can
require many tests and carry a significant overhead.
Using the Debuggers to Locate an Exception
This section gives examples showing how to use dbx (source-level
debugger) and adb (assembly-level debugger) to investigate the cause of
a floating-point exception and locate the instruction that raised it.
Recall that in order to use the source-level debugging features of dbx,
programs should be compiled with the -g flag. Refer to the
WorkShop: Command-Line Utilities guide, the chapters
on debugging, for more information.
program ex double precision x, y, routine x = -4.2d0 y = routine(x) print * , x, y end double precision function routine(x) double precision x routine = sqrt(x) - 1.0d0 return end |
Compiling and running this program produces:
-4.2000000000000 NaN Note: IEEE floating-point exception flags raised: Inexact; Invalid Operation; See the Numerical Computation Guide, ieee_flags(3M) |
To determine the cause of the invalid operation exception, you can
recompile with the -ftrap option to enable trapping on
invalid operations and use either dbx or adb to
locate the site at which a SIGFPE signal is delivered.
Alternatively, you can use adb or dbx without
recompiling the program by linking with a startup routine that enables
the invalid operation trap or by manually enabling the trap.
Using dbx to Locate the Instruction Causing an
Exception
The simplest way to locate the code that causes a floating-point
exception is to recompile with the -g and -ftrap
flags and then use dbx to track down the location where the exception
occurs. First, recompile the program as follows:
example% f77 -g -ftrap=invalid ex.f |
Compiling with -g allows you to use the source-level
debugging features of dbx. Specifying
-ftrap=invalid causes the program to run with trapping
enabled for invalid operation exceptions.
The output shows that the exception occured in the routine
function as a result of attempting to take the square root of a
negative number.
Using adb to Locate the Instruction Causing an
Exception
You can also use adb to identify the cause of an exception,
although adb can't locate the source file and line
number as dbx can. Again, the first step is to recompile
with -ftrap:
example% f77 -ftrap=invalid ex.f |
The output shows that the exception was caused by an fsqrtd
instruction. Examining the source register shows that the exception
was a result of attempting to take the square root of a negative
number.
The output reveals that the exception was caused by a fsqrt
instruction; examination of the floating point registers reveals that
the exception was a result of attempting to take the square root of a
negative number.
The preceding output shows that the exception was caused by attempting
to take the square root of a negative number.
Enabling Traps Without Recompilation
In the preceding examples, trapping on invalid operation exceptions was
enabled by recompiling the main subprogram with the -ftrap
flag. In some cases, recompiling the main program may not be possible,
so you may need to resort to other means to enable trapping. There are
several ways to do this.
#include <ieeefp.h> #pragma init (trapinvalid) void trapinvalid() { /* FP_X_INV et al are defined in ieeefp.h */ fpsetmask(FP_X_INV); } |
Now compile this file to create an object file and link the original
program with this object file:
example% cc -c init.c example% f77 ex.o init.o example% a.out Floating point exception 7, invalid operand, occurred at address 8048afd. Abort |
Even if relinking is not possible, you can still enable traps manually
while running dbx or adb by directly modifying
the floating point status register. This can be somewhat tricky
because the Solaris operating system does not enable the floating point
unit until the first time it is used within a program, at which point
the floating point status register is reset with all traps disabled.
Thus, you cannot manually enable trapping until after the program has
executed at least one floating point instruction.
Using a Signal Handler to Locate an Exception
The previous section presented several methods for enabling trapping at
the outset of a program in order to locate the first occurrence of an
exception. In contrast, you can isolate any particular occurrence of
an exception by enabling trapping within the program itself. If you
enable trapping but do not install a SIGFPE handler, the
program will abort on the next occurrence of the trapped exception.
Alternatively, if you install a SIGFPE handler, the next
occurrence of the trapped exception will cause the system to transfer
control to the handler, which can then print diagnostic information,
such as the address of the instruction where the exception occurred,
and either abort or resume execution. (In order to resume execution
with any prospect for a meaningful outcome, the handler may need to
supply a result for the exceptional operation as described in the next
section.) ieee_handler(3m)
The syntax of a call to ieee_handler is: i = ieee_handler(action, exception, handler)
The two input parameters action and exception are strings.
The third input parameter, handler, is of type
sigfpe_handler_type, which is defined in floatingpoint.h
(or f77_floatingpoint.h for FORTRAN programs).
When the requested action is "set", ieee_handler
establishes the handling function specified by handler for the
specified exception. The handling function may be
SIGFPE_DEFAULT or SIGFPE_IGNORE, both of which
select the default IEEE behavior, SIGFPE_ABORT, which causes
the program to abort on the occurrence of any of the named exceptions,
or the address of a user-supplied subroutine, which causes that
subroutine to be invoked (with the parameters described in the
sigaction(2) manual page for a signal handler installed with
the SA_SIGINFO flag set) when any of the named exceptions
occurs. If the handler is SIGFPE_DEFAULT or
SIGFPE_IGNORE, ieee_handler also disables
trapping on the specified exceptions; for any other handler,
ieee_handler enables trapping.
#include <sunmath.h> if (ieee_handler("set", "division", SIGFPE_ABORT) != 0) printf("ieee trapping not supported here \n"); |
Here is the equivalent FORTRAN code:
#include <f77_floatingpoint.h> i = ieee_handler('set', 'division', SIGFPE_ABORT) if(i.ne.0) print *,'ieee trapping not supported here' |
This C fragment restores IEEE default exception handling for all
exceptions:
#include <sunmath.h> if (ieee_handler("clear", "all", 0) != 0) printf("could not clear exception handlers\n"); |
Here is the same action in FORTRAN:
i = ieee_handler('clear', 'all', 0) if (i.ne.0) print *, 'could not clear exception handlers' |
Reporting an Exception from a Signal Handler
When a SIGFPE handler installed via ieee_handler is invoked,
the operating system provides additional information indicating the
type of exception that occurred, the address of the instruction that
caused it, and the contents of the machine's integer and floating
point registers. The handler can examine this information and print a
message identifying the exception and the location at which it
occurred.
#include <siginfo.h> #include <ucontext.h> void handler(int sig, siginfo_t *sip, ucontext_t *uap) { ... } |
When the handler is invoked, the sig parameter contains the number of
the signal that was sent. Signal numbers are defined in
sys/signal.h; the SIGFPE signal number is 8.
SIGFPE Type |
IEEE Type |
FPE_INTDIV |
|
FPE_INTOVF |
|
FPE_FLTRES |
inexact |
FPE_FLTDIV |
division |
FPE_FLTUND |
underflow |
FPE_FLTINV |
invalid |
FPE_FLTOVF |
overflow |
As the table shows, each type of IEEE floating point exception has a
corresponding SIGFPE signal type. Integer division by zero
(FPE_INTDIV) and integer overflow (FPE_INTOVF)
are also included among the SIGFPE types, but because they
are not IEEE floating point exceptions you cannot install handlers for
them via ieee_handler. (You can install handlers for these
SIGFPE types via sigfpe(3); note, though, that
integer division by zero is either ignored or generates
SIGILL on PowerPC systems, and integer overflow is ignored
on all SPARC, Intel, and PowerPC systems. On SPARC and Intel systems,
special instructions can cause the delivery of a SIGFPE
signal of type FPE_INTOVF, but Sun compilers do not generate
these instructions.)
On SPARC and PowerPC systems, the output from this program resembles
the following:
Note that the instruction that causes the exception need not deliver
the IEEE default result when trapping is enabled: in the preceding
outputs, the value reported for max_normal * max_normal is
not the default result for an operation that overflows (i.e., a
correctly signed infinity). In general, a SIGFPE handler
must supply a result for an operation that causes a trapped exception
in order to continue the computation with meaningful values. The next
section shows how this can be done.
Handling Exceptions
Historically, most numerical software has been written without regard
to exceptions (for a variety of reasons), and many programmers have
become accustomed to environments in which exceptions cause a program
to abort immediately. Now, some high-quality software packages such as
LAPACK are being carefully designed to avoid exceptions such as
division by zero and invalid operations and to scale their inputs
aggressively to preclude overflow and potentially harmful underflow.
Neither of these approaches to dealing with exceptions is appropriate
in every situation, however: ignoring exceptions can pose problems when
one person writes a program or subroutine that is intended to be used
by someone else (perhaps someone who does not have access to the source
code), and attempting to avoid all exceptions can require many
defensive tests and branches and carry a significant cost (see Demmel
and Li, "Faster Numerical Algorithms via Exception Handling",
IEEE Trans. Comput. 43 (1994), pp. 983-992.)
#include <siginfo.h> #include <ucontext.h> void handler(int sig, siginfo_t *sip, ucontext_t *uap) { ... } |
When a SIGFPE signal handler is invoked as a result of a
trapped floating point exception, the uap parameter points to a data
structure that contains a copy of the machine's integer and
floating point registers as well as other system-dependent information
describing the exception. If the signal handler returns normally, the
saved data are restored and the program resumes execution at the point
at which the trap was taken. Thus, by accessing and decoding the
information in the data structure that describes the exception and
possibly modifying the saved data, a SIGFPE handler can
substitute a user-supplied value for the result of an exceptional
operation and continue computation. As an illustration, the following
examples show how to subsitute a scaled result for an operation that
underflows or overflows.
Substituting IEEE Trapped Under/Overflow Results
The IEEE standard recommends that when underflow and overflow are
trapped, the system should provide a way for a trap handler to
substitute an exponent-wrapped result, i.e., a value that agrees with
what would have been the rounded result of the operation that
underflowed or overflowed except that the exponent is wrapped around
the end of its usual range, thereby effectively scaling the result by a
power of two. The scale factor is chosen to map underflowed and
overflowed results as nearly as possible to the middle of the exponent
range so that subsequent computations will be less likely to underflow
or overflow further. By keeping track of the number of underflows and
overflows that occur, a program can scale the final result to
compensate for the exponent wrapping. This under/overflow
"counting mode" can be used to produce accurate results in
computations that would otherwise exceed the range of the available
floating point formats. (See P. Sterbenz, Floating-Point
Computation.)
The output from the preceding program is:
159.309 1.59309e-28 1 4.14884e+137 4.14884e-163 1 Note: IEEE floating-point exception traps enabled: underflow; overflow; See the Numerical Computation Guide, ieee_handler(3M) |
.inline dummyf,1 flds (%esp) .end .inline dummy,2 fldl (%esp) .end |
As on SPARC, the output from the preceding program on Intel is:
159.309 1.59309e-28 1 4.14884e+137 4.14884e-163 1 Note: IEEE floating-point exception traps enabled: underflow; overflow; See the Numerical Computation Guide, ieee_handler(3M) |
159.309 1.59309e-28 1 4.14884e+137 4.14884e-163 1 Note: IEEE floating-point exception traps enabled: underflow; overflow; See the Numerical Computation Guide, ieee_handler(3M) |