Numerical Computation Guide |
Exceptions and Exception Handling
This chapter describes IEEE floating point exceptions and shows how to detect, locate, and handle them.
The floating point environment provided by the Sun WorkShop 6 compilers and the Solaris operating environment on SPARC and x86 platforms supports all of the exception handling facilities required by the IEEE standard as well as many of the recommended optional facilities. One objective of these facilities is explained in the IEEE 854 Standard (IEEE 854, page 18):
- ... to minimize for users the complications arising from exceptional conditions. The arithmetic system is intended to continue to function on a computation as long as possible, handling unusual situations with reasonable default responses, including setting appropriate flags.
To achieve this objective, the standards specify default results for exceptional operations and require that an implementation provide status flags, which can be sensed, set, or cleared by a user, to indicate that exceptions have occurred. The standards also recommend that an implementation provide a means for a program to trap (i.e., interrupt normal control flow) when an exception occurs. The program can optionally supply a trap handler that handles the exception in an appropriate manner, for example by providing an alternate result for the exceptional operation and resuming execution. This chapter lists the exceptions defined by IEEE 754 along with their default results and describes the features of the floating point environment that support status flags, trapping, and exception handling.
What Is an Exception?
It is hard to define exceptions. To quote W. Kahan,
- An arithmetic exception arises when an attempted atomic arithmetic operation has no result that would be acceptable universally. The meanings of atomic and acceptable vary with time and place. (See Handling Arithmetic Exceptions by W. Kahan.)
For example, an exception arises when a program attempts to take the square root of a negative number. (This example is one case of an invalid operation exception.) When such an exception occurs, the system responds in one of two ways:
- If the exception's trap is disabled (the default case), the system records the fact that the exception occurred and continues executing the program using the default result specified by IEEE 754 for the excepting operation.
- If the exception's trap is enabled, the system generates a
SIGFPE
signal. If the program has installed aSIGFPE
signal handler, the system transfers control to that handler; otherwise, the program aborts.IEEE 754 defines five basic types of floating point exceptions: invalid operation, division by zero, overflow, underflow and inexact. The first three (invalid, division, and overflow) are sometimes collectively called common exceptions. These exceptions can seldom be ignored when they occur.
ieee_handler
(3m) gives an easy way to trap on common exceptions only. The other two exceptions (underflow and inexact) are seen more often--in fact, most floating point operations incur the inexact exception--and they can usually, though not always, be safely ignored.TABLE 4-1 condenses information found in IEEE Standard 754. It describes the five floating point exceptions and the default response of an IEEE arithmetic environment when these exceptions are raised.
Notes for Table 4-1
- Unordered comparison: Any pair of floating point values can be compared, even if they are not of the same format. Four mutually exclusive relations are possible: less than, greater than, equal, or unordered. Unordered means that at least one of the operands is a NaN (not a number).
- Every NaN compares "unordered" with everything, including itself. TABLE 4-2 shows which predicates cause the invalid operation exception when the relation is unordered.
TABLE 4-2 Unordered Comparisons = == .EQ.no != .NE.no > > .GT.yes >= .GE.yes < < .LT.yes <= .LE.yes
- Invalid conversion: Attempt to convert NaN or infinity to an integer, or integer overflow on conversion from floating point format.
- The smallest normal numbers representable in the IEEE single, double, and extended formats are 2-126, 2-1022, and 2-16382, respectively. See Chapter 2 for a description of the IEEE floating point formats.
The x86 floating point environment provides another exception not mentioned in the IEEE standards: the denormal operand exception. This exception is raised whenever a floating point operation is performed on a subnormal number.
Exceptions are prioritized in the following order: invalid (highest priority), overflow, division, underflow, inexact (lowest priority). On x86 platforms, the denormal operand exception has the lowest priority of all.
The only combinations of standard exceptions that can occur simultaneously are overflow with inexact and underflow with inexact. On x86, the denormal operand exception can occur with any of the five standard exceptions. If trapping on overflow, underflow, and inexact is enabled, the overflow and underflow traps take precedence over the inexact trap; they all take precedence over a denormal operand trap on x86.
Detecting Exceptions
As required by the IEEE standard, the floating point environments on SPARC and x86 platforms provide status flags that record the occurrence of floating point exceptions. A program can test these flags to determine which exceptions have occurred. The flags can also be explicitly set and cleared. The
ieee_flags
function provides one way to access these flags. In programs written in C or C++, the C99 floating point environment functions inlibm9x.so
provide another.On SPARC, each exception has two flags associated with it, current and accrued. The current exception flags always indicate the exceptions raised by the last floating point instruction to complete execution. These flags are also accumulated (i.e., ``or''-ed) into the accrued exception flags thereby providing a record of all untrapped exceptions that have occurred since the program began execution or since the accrued flags were last cleared by the program. (When a floating point instruction incurs a trapped exception, the current exception flag corresponding to the exception that caused the trap is set, but the accrued flags are unchanged.) Both the current and accrued exception flags are contained in the floating point status register,
%fsr
.On x86, the floating point status word (SW) provides flags for accrued exceptions as well as flags for the status of the floating point stack.
ieee_flags
(3m)The syntax for a call to
ieee_flags
(3m) is:
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 outcall 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:
"not available"
-- if information on exceptions is not available""
(an empty string) -- if there are no accrued exceptions or, in the case of x86, the denormal operand is the only accrued exception- the name of the exception named in the third argument, in, if that exception has occurred
- otherwise, the name of the highest priority exception that has occurred.
For example, in the Fortran call:
character*8 outi = ieee_flags('get', 'exception', 'division', out)
the string returned in
out
is "division
" if the division-by-zero exception has occurred; otherwise it is the name of the highest priority exception that has occurred. Note that in is ignored unless it names a particular exception; for example, the argument "all
" is ignored in the C call:
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 thefp_exception_type
values defined in the filesys/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.
C99 Exception Flag Functions
C/C++ programs can test, set, and clear the floating point exception flags using the C99 floating point environment functions in
libm9x.so
. The header filefenv.h
defines five macros corresponding to the five standard exceptions:FE_INEXACT
,FE_UNDERFLOW
,FE_OVERFLOW
,FE_DIVBYZERO
, andFE_INVALID
. It also defines the macroFE_ALL_EXCEPT
to be the bitwise "or" of all five exception macros. These macros can be combined to test or clear any subset of the exception flags or raise any combination of exceptions. The following examples show the use of these macros with several of the C99 floating point environment functions; see thefeclearexcept
(3M) manual page for more information. (Note: For consistent behavior, do not use both the C99 floating point environment functions and extensions inlibm9x.so
and theieee_flags
andieee_handler
functions inlibsunmath
in the same program.)To clear all five exception flags:
feclearexcept(FE_ALL_EXCEPT);To test whether the invalid operation or division by zero flags have been raised:
int i;i = fetestexcept(FE_INVALID | FE_DIVBYZERO);if (i & FE_INVALID)/* invalid flag was raised */else if (i & FE_DIVBYZERO)/* division by zero flag was raised */To simulate raising an overflow exception (note that this will provoke a trap if the overflow trap is enabled):
feraiseexcept(FE_OVERFLOW);The
fegetexceptflag
andfesetexceptflag
functions provide a way to save and restore a subset of the flags. The next example shows one way to use these functions.
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? 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.
An easier way to determine where an exception occurs is to enable its trap. When an exception whose trap is enabled occurs, the operating system notifies the program by sending a
SIGFPE
signal (see thesignal
(5) manual page). Thus, by enabling trapping for an exception, you can determine where the exception occurs either by running under a debugger and stopping on receipt of aSIGFPE
signal or by establishing aSIGFPE
handler that prints the address of the instruction where the exception occurred. Note that trapping must be enabled for an exception to generate aSIGFPE
signal; when trapping is disabled and an exception occurs, the corresponding flag is set and execution continues with the default result specified in TABLE 4-1, but no signal is delivered.Using the Debuggers to Locate an Exception
This section gives examples showing how to use
dbx
(source-level debugger) andadb
(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 ofdbx
, programs should be compiled with the-g
flag. Refer to the Debugging a Program With Sun WorkShop manual for more information.Consider the following program:
program exdouble precision x, y, sqrtm1x = -4.2d0y = sqrtm1(x)print * , x, yenddouble precision function sqrtm1(x)double precision xsqrtm1
= sqrt(x) - 1.0d0returnendCompiling and running this program produces:
-4.2000000000000 NaNNote: 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 eitherdbx
oradb
to locate the site at which aSIGFPE
signal is delivered. Alternatively, you can useadb
ordbx
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 ExceptionThe simplest way to locate the code that causes a floating point exception is to recompile with the
-g
and-ftrap
flags and then usedbx
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 ofdbx
. Specifying-ftrap=invalid
causes the program to run with trapping enabled for invalid operation exceptions. Next, invokedbx
, issue thecatch fpe
command to stop when aSIGFPE
is issued, and run the program. The result is:
The output shows that the exception occurred in the
sqrtm1
function as a result of attempting to take the square root of a negative number.Using
adb
to Locate the Instruction Causing an ExceptionYou can also use
adb
to identify the cause of an exception, althoughadb
can't locate the source file and line number asdbx
can. Again, the first step is to recompile with-ftrap
:
example%f77 -ftrap=invalid ex.f
Now invoke
adb
and run the program. When an invalid operation exception occurs,adb
stops at an instruction following the one that caused the exception. To find the instruction that caused the exception, disassemble several instructions and look for the last floating point instruction prior to the instruction at whichadb
has stopped. On SPARC, the result might resemble the following transcript.
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.On x86, because instructions do not have a fixed length, finding the correct address from which to disassemble the code might involve some trial and error. In this example, the exception occurs close to the beginning of a function, so we can disassemble from there. The following might be a typical result
.
example%adb a.out
:r
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.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 might not be possible, so you might need to resort to other means to enable trapping. There are several ways to do this.When you are using
dbx
, you can enable traps manually by directly modifying the floating point status register. On SPARC, this can be somewhat tricky because the 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. In our example, the floating point unit has already been accessed by the time thesqrtm1
function is called, so we can set a breakpoint on entry to that function, enable trapping on invalid operation exceptions, instructdbx
to stop on the receipt of aSIGFPE
signal, and continue execution. The steps are as follows (note the use of theassign
command to modify the%fsr
to enable trapping on invalid operation exceptions):
On x86, the startup code that the compiler automatically links into every program initializes the floating point unit before transferring control to the main program. Thus, you can manually enable trapping at any time after the main program begins. The following example shows the steps involved:
You can also enable trapping without recompiling the main program or using
dbx
by establishing an initialization routine that enables traps. (This might be useful, for example, if you want to abort the program when an exception occurs without running under a debugger.) There are two ways to establish such a routine.If the object files and libraries that comprise the program are available, you can enable trapping by relinking the program with an appropriate initialization routine. First, create a C source file similar to the following:
#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 address8048afd.AbortIf relinking is not possible but the program has been dynamically linked, you can enable trapping by using the shared object preloading facility of the runtime linker. To do this on SPARC systems, create the same C source file as above, but compile as follows:
example%cc -Kpic -G -ztext init.c -o init.so -lc
Now to enable trapping, add the path name of the
init.so
object to the list of preloaded shared objects specified by the environment variableLD_PRELOAD
, for example:
example%env LD_PRELOAD=./init.so
a.out
Floating point exception 7, invalid operand, occurred at address10c24.Abort(See the Linker and Libraries Guide for more information about creating and preloading shared objects.)
In principle, you can change the way any floating point control modes are initialized by preloading a shared object as described above. Note, though, that initialization routines in shared objects, whether preloaded or explicitly linked, are executed by the runtime linker before it passes control to the startup code that is part of the main executable. The startup code then establishes any nondefault modes selected via the
-ftrap
,-fround
,-fns
(SPARC), or-fprecision
(x86) compiler flags, executes any initialization routines that are part of the main executable (including those that are statically linked), and finally passes control to the main program. Therefore, on SPARC (i) any floating point control modes established by initialization routines in shared objects, such as the traps enabled in the example above, will remain in effect throughout the execution of the program unless they are overridden; (ii) any nondefault modes selected via the compiler flags will override modes established by initialization routines in shared objects (but default modes selected via compiler flags will not override previously established modes); and (iii) any modes established either by initialization routines that are part of the main executable or by the main program itself will override both.On x86, the situation is complicated by the fact that the system kernel initializes the floating point hardware with some nondefault modes whenever a new process is begun, but the startup code automatically supplied by the compiler resets some of those modes to the default before passing control to the main program. Therefore, initialization routines in shared objects, unless they change the floating point control modes, run with trapping enabled for invalid operation, division by zero, and overflow exceptions and with the rounding precision set to round to 53 significant bits. Once the runtime linker passes control to the startup code, this code calls the routine
__fpstart
(found in the standard C library,libc
), which disables all traps and sets the rounding precision to 64 significant bits. The startup code then establishes any nondefault modes selected by the-fround
,-ftrap
, or-fprecision
flags before executing any statically linked initialization routines and passing control to the main program. As a consequence, in order to enable trapping (or change the rounding precision mode) on x86 platforms by preloading a shared object with an initialization routine, you must override the__fpstart
routine so that it does not reset the trap enable and rounding precision modes. The substitute__fpstart
routine should still perform the rest of the initialization functions that the standard routine does. However, the following code shows one way to do this.
Compiling this source file to a shared object and preloading it gives the expected results:
example%cc -Kpic -G -ztext init.c -o init.so -lc
example%env LD_PRELOAD=./init.so
a.out
Floating point exception 7, invalid operand, occurred at address8048afd.AbortUsing 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 aSIGFPE
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 might need to supply a result for the exceptional operation as described in the next section.)You can use
ieee_handler
to simultaneously enable trapping on any of the five IEEE floating point exceptions and either request that the program abort when the specified exception occurs or establish aSIGFPE
handler. You can also install aSIGFPE
handler using one of the lower-level functionssigfpe
(3),signal
(3c), orsigaction
(2); however, these functions do not enable trapping asieee_handler
does. (Remember that a floating point exception triggers aSIGFPE
signal only when its trap is enabled.)
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 infloatingpoint.h
(orf77_floatingpoint.h
for Fortran programs).The three input parameters can take the following values:
action char *
get
,set
,clear
exception char *
invalid
,division
,overflow,
,
underflowinexact,
,
allcommon
handler sigfpe_handler_type
user-defined routine
SIGFPE_DEFAULT
SIGFPE_IGNORE
SIGFPE_ABORT
When the requested action is "
set
",ieee_handler
establishes the handling function specified by handler for the exceptions named by exception. The handling function can beSIGFPE_DEFAULT
orSIGFPE_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 thesigaction
(2) manual page for a signal handler installed with theSA_SIGINFO
flag set) when any of the named exceptions occurs. If the handler isSIGFPE_DEFAULT
orSIGFPE_IGNORE
,ieee_handler
also disables trapping on the specified exceptions; for any other handler,ieee_handler
enables trapping. (On x86 platforms, the floating point hardware traps whenever an exception's trap is enabled and its corresponding flag is raised. Therefore, to avoid spurious traps, a program should clear the flag for each specified exception before callingieee_handler
to enable trapping.)When the requested action is "
clear
",ieee_handler
revokes whatever handling function is currently installed for the specified exception and disables its trap. (This is the same as "set
"tingSIGFPE_DEFAULT
.) The third parameter is ignored when action is "clear
".For both the "
set
" and "clear
" actions,ieee_handler
returns 0 if the requested action is available and a nonzero value otherwise.When the requested action is "
get
",ieee_handler
returns the address of the handler currently installed for the specified exception (orSIGFPE_DEFAULT
, if no handler is installed).The following examples show a few code fragments illustrating the use of
ieee_handler
. This C code causes the program to abort on division by zero:
Here is the equivalent Fortran code:
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 viaieee_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.To access the information supplied by the system, declare the handler as follows. (The remainder of this chapter presents sample code in C; see Appendix A for examples of
SIGFPE
handlers in Fortran.)
#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
; theSIGFPE
signal number is 8.The sip parameter points to a structure that records additional information about the signal. For a
SIGFPE
signal, the relevant members of this structure aresip->si_code
andsip->si_addr
(seesys/siginfo.h
). The significance of these members depends on the system and on what event triggered theSIGFPE
signal.The
sip->si_code
member is one of theSIGFPE
signal types listed in TABLE 4-4. (The tokens shown are defined insys/machsig.h
.)
TABLE 4-4 Types for Arithmetic Exceptions 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 theSIGFPE
types, but because they are not IEEE floating point exceptions you cannot install handlers for them viaieee_handler
. (You can install handlers for theseSIGFPE
types viasigfpe
(3); note, though, that integer overflow is ignored by default on all SPARC and x86 platforms. Special instructions can cause the delivery of aSIGFPE
signal of typeFPE_INTOVF
, but Sun compilers do not generate these instructions.)For a
SIGFPE
signal corresponding to an IEEE floating point exception, thesip->si_code
member indicates which exception occurred on SPARC systems, while on x86 platforms it indicates the highest priority exception whose flag is raised (excluding the denormal operand flag). Thesip->si_addr
member holds the address of the instruction that caused the exception on SPARC systems, and on x86 platforms it holds the address of the instruction at which the trap was taken (usually the next floating point instruction following the one that caused the exception).Finally, the uap parameter points to a structure that records the state of the system at the time the trap was taken. The contents of this structure are system-dependent; see
sys/reg.h
for definitions of some of its members.Using the information provided by the operating system, we can write a
SIGFPE
handler that reports the type of exception that occurred and the address of the instruction that caused it. CODE EXAMPLE 4-1 shows such a handler.
On SPARC systems, the output from this program resembles the following:
min_normal = 2.22507e-308min_normal / 13.0 = 1.7116e-309max_normal = 1.79769e+308fp exception 4 at address 10d0cmax_normal * max_normal = 1.79769e+308Note: IEEE floating-point exception flags raised:
Inexact; Underflow;
IEEE floating-point exception traps enabled:
overflow; division by zero; invalid operation;
See the Numerical Computation Guide, ieee_flags(3M), ieee_handler(3M)
On x86 platforms, the operating system saves a copy of the accrued exception flags and then clears them before invoking a
SIGFPE
handler. Unless the handler takes steps to preserve them, the accrued flags are lost once the handler returns. Thus, the output from the preceding program does not indicate that an underflow exception was raised:
min_normal = 2.22507e-308min_normal / 13.0 = 1.7116e-309max_normal = 1.79769e+308fp exception 4 at address 8048fe6max_normal * max_normal = 1.79769e+308Note: IEEE floating-point exception traps enabled:overflow; division by zero; invalid operation;See the Numerical Computation Guide, ieee_handler(3M)In most cases, the instruction that causes the exception does 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, aSIGFPE
handler must supply a result for an operation that causes a trapped exception in order to continue the computation with meaningful values. See Handling Exceptions for one way to do this.Using
libm9x.so
Exception Handling Extensions to Locate an ExceptionC/C++ programs can use the exception handling extensions to the C99 floating point environment functions in
libm9x.so
to locate exceptions in several ways. These extensions include functions that can establish handlers and simultaneously enable traps, just asieee_handler
does, but they provide more flexibility. They also support logging of retrospective diagnostic messages regarding floating point exceptions to a selected file.
fex_set_handling
(3m)The
fex_set_handling
function allows you to select one of several options, or modes, for handling each type of floating point exception. The syntax of a call tofex_set_handling
is:
ret = fex_set_handling(
ex, mode, handler);
The ex argument specifies the set of exceptions to which the call applies. It must be a bitwise "or" of the values listed in the first column of TABLE 4-5. (These values are defined in
fenv.h
.
TABLE 4-5 Exception codes for fex_set_handling
FEX_INEXACT
inexact result FEX_UNDERFLOW
underflow FEX_OVERFLOW
overflow FEX_DIVBYZERO
division by zero FEX_INV_ZDZ
0/0 invalid operation FEX_INV_IDI
infinity/infinity invalid operation FEX_INV_ISI
infinity-infinity invalid operation FEX_INV_ZMI
0*infinity invalid operation FEX_INV_SQRT
square root of negative number FEX_INV_SNAN
operation on signaling NaN FEX_INV_INT
invalid integer conversion FEX_INV_CMP
invalid unordered comparison
)For convenience,
fenv.h
also defines the following values:FEX_NONE
(no exceptions),FEX_INVALID
(all invalid operation exceptions),FEX_COMMON
(overflow, division by zero, and all invalid operations), andFEX_ALL
(all exceptions).The mode argument specifies the exception handling mode to be established for the indicated exceptions. There are five possible modes:
FEX_NONSTOP
mode provides the IEEE 754 default nonstop behavior. This is equivalent to leaving the exception's trap disabled. (Note that unlikeieee_handler
,fex_set_handling
allows you to establish nondefault handling for certain types of invalid operation exceptions and retain IEEE default handling for the rest.)FEX_NOHANDLER
mode is equivalent to enabling the exception's trap without providing a handler. When an exception occurs, the system transfers control to a previously installedSIGFPE
handler, if present, or aborts.FEX_ABORT
mode causes the program to callabort
(3c) when the exception occurs.FEX_SIGNAL
installs the handling function specified by the handler argument for the indicated exceptions. When any of these exceptions occurs, the handler is invoked with the same arguments as if it had been installed byieee_handler
.FEX_CUSTOM
installs the handling function specified by handler for the indicated exceptions. UnlikeFEX_SIGNAL
mode, when an exception occurs, the handler is invoked with a simplified argument list. The arguments consist of an integer whose value is one of the values listed in TABLE 4-5 and a pointer to a structure that records additional information about the operation that caused the exception. The contents of this structure are described in the next section and in thefex_set_handling
(3m) manual page.Note that the handler parameter is ignored if the specified mode is
FEX_NONSTOP
,FEX_NOHANDLER
, orFEX_ABORT
.fex_set_handling
returns a nonzero value if the specified mode is established for the indicated exceptions, and returns zero otherwise. (In the examples below, the return value is ignored.)The following examples suggest ways to use
fex_set_handling
to locate certain types of exceptions. To abort on a 0/0 exception:
fex_set_handling(FEX_INV_ZDZ, FEX_ABORT, NULL);To install a
SIGFPE
handler for overflow and division by zero:
fex_set_handling(FEX_OVERFLOW | FEX_DIVBYZERO, FEX_SIGNAL,
handler);In the previous example, the handler function could print the diagnostic information supplied via the sip parameter to a
SIGFPE
handler, as shown in the previous subsection. By contrast, the following example prints the information about the exception that is supplied to a handler installed inFEX_CUSTOM
mode. (See thefex_set_handling
(3m) manual page for more information.)
CODE EXAMPLE 4-2 Printing Information Supplied to Handler Installed in FEX_CUSTOM Mode
#include <fenv.h> void handler(int ex, fex_info_t *info) { switch (ex) { case FEX_OVERFLOW: printf("Overflow in "); break; case FEX_DIVBYZERO: printf("Division by zero in "); break; default: printf("Invalid operation in "); } switch (info->op) { case fex_add: printf("floating point add\n"); break; case fex_sub: printf("floating point subtract\n"); break; case fex_mul: printf("floating point multiply\n"); break; case fex_div: printf("floating point divide\n"); break; case fex_sqrt: printf("floating point square root\n"); break; case fex_cnvt: printf("floating point conversion\n"); break; case fex_cmp: printf("floating point compare\n"); break; default: printf("unknown operation\n"); } switch (info->op1.type) { case fex_int: printf("operand 1: %d\n", info->op1.val.i); break; case fex_llong: printf("operand 1: %lld\n", info->op1.val.l); break; case fex_float: printf("operand 1: %g\n", info->op1.val.f); break; case fex_double: printf("operand 1: %g\n", info->op1.val.d); break; case fex_ldouble: printf("operand 1: %Lg\n", info->op1.val.q); break; } switch (info->op2.type) { case fex_int: printf("operand 2: %d\n", info->op2.val.i); break; case fex_llong: printf("operand 2: %lld\n", info->op2.val.l); break; case fex_float: printf("operand 2: %g\n", info->op2.val.f); break; case fex_double: printf("operand 2: %g\n", info->op2.val.d); break; case fex_ldouble: printf("operand 2: %Lg\n", info->op2.val.q); break; } } ... fex_set_handling(FEX_COMMON, FEX_CUSTOM, handler);The handler in the preceding example reports the type of exception that occurred, the type of operation that caused it, and the operands. It does not indicate where the exception occurred. To find out where the exception occurred, you can use retrospective diagnostics.
Retrospective Diagnostics
Another way to locate an exception using the
libm9x.so
exception handling extensions is to enable logging of retrospective diagnostic messages regarding floating point exceptions. When you enable logging of retrospective diagnostics, the system records information about certain exceptions. This information includes the type of exception, the address of the instruction that caused it, the manner in which it will be handled, and a stack trace similar to that produced by a debugger. (The stack trace recorded with a retrospective diagnostic message contains only instruction addresses and function names; for additional debugging information such as line numbers, source file names, and argument values, you must use a debugger.)The log of retrospective diagnostics does not contain information about every single exception that occurs; if it did, a typical log would be huge, and it would be impossible to isolate unusual exceptions. Instead, the logging mechanism eliminates redundant messages. A message is considered redundant under either of two circumstances:
- The same exception has been previously logged at the same location (i.e., with the same instruction address and stack trace), or
FEX_NONSTOP
mode is in effect for the exception and its flag has been previously raised.In particular, in most programs, only the first occurrence of each type of exception will be logged. (When
FEX_NONSTOP
handling mode is in effect for an exception, clearing its flag via any of the C99 floating point environment functions allows the next occurrence of that exception to be logged, provided it does not occur at a location at which it was previously logged.)To enable logging, use the
fex_set_log
function to specify the file to which messages should be delivered. For example, to log messages to the standard error file, use:
fex_set_log(stderr);The following example combines logging of retrospective diagnostics with the shared object preloading facility illustrated in the previous section. By creating the following C source file, compiling it to a shared object, preloading the shared object by supplying its path name in the
LD_PRELOAD
environment variable, and specifying the names of one or more exceptions (separated by commas) in theFTRAP
environment variable, you can simultaneously abort the program on the specified exceptions and obtain retrospective diagnostic output showing where each exception occurs.
CODE EXAMPLE 4-3 Combined Logging of Retrospective Diagnostics With Shared Object Preloading
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fenv.h> static struct ftrap_string { const char *name; int value; } ftrap_table[] = { { "inexact", FEX_INEXACT }, { "division", FEX_DIVBYZERO }, { "underflow", FEX_UNDERFLOW }, { "overflow", FEX_OVERFLOW }, { "invalid", FEX_INVALID }, { NULL, 0 } }; #pragma init (set_ftrap) void set_ftrap() { struct ftrap_string *f; char *s, *s0; int ex = 0; if ((s = getenv("FTRAP")) == NULL) return; if ((s0 = strtok(s, ",")) == NULL) return; do { for (f = &trap_table[0]; f->name != NULL; f++) { if (!strcmp(s0, f->name)) ex |= f->value; } } while ((s0 = strtok(NULL, ",")) != NULL); fex_set_handling(ex, FEX_ABORT, NULL); fex_set_log(stderr); }Using the preceding code with the example program given at the beginning of this section produces the following results on SPARC:
example%cc -Kpic -G -ztext init.c -o init.so -R/opt/SUNWspro/lib -L/opt/SUNWspro/lib -lm9x -lc
example%env FTRAP=invalid LD_PRELOAD=./init.so
a.out
Floating point invalid operation (sqrt) at 0x00010c24 sqrtm1_, abort0x00010c30 sqrtm1_0x00010b48 MAIN_0x00010ccc mainAbortThe preceding output shows that the invalid operation exception was raised as a result of a square root operation in the routine
sqrtm1
.(As noted above, to enable trapping from an initialization routine in a shared object on x86 platforms, you must override the standard
__fpstart
routine.)Appendix A gives more examples showing typical log outputs. For general information, see the
fex_set_log
(3m) man page.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.)
The default exception response, status flags, and optional trapping facility of IEEE arithmetic are intended to provide a third alternative: continuing a computation in the presence of exceptions and either detecting them after the fact or intercepting and handling them as they occur. As described above,
ieee_flags
or the C99 floating point environment functions can be used to detect exceptions after the fact, andieee_handler
orfex_set_handling
can be used to enable trapping and install a handler to intercept exceptions as they occur. In order to continue the computation, however, the IEEE standard recommends that a trap handler be able to provide a result for the operation that incurred an exception. ASIGFPE
handler installed viaieee_handler
orfex_set_handling
inFEX_SIGNAL
mode can accomplish this using the uap parameter supplied to a signal handler by the Solaris operating environment. AnFEX_CUSTOM
mode handler installed viafex_set_handling
can provide a result using the info parameter supplied to such a handler.Recall that a
SIGFPE
signal handler can be declared in C as follows:
#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, aSIGFPE
handler can substitute a user-supplied value for the result of an exceptional operation and continue computation.An
FEX_CUSTOM
mode handler can be declared as follows:
#include <fenv.h>void handler(int ex, fex_info_t *info){...}When a
FEX_CUSTOM
handler is invoked, the ex parameter indicates which type of exception occurred (it is one of the values listed in TABLE 4-5) and the info parameter points to a data structure that contains more information about the exception. Specifically, this structure contains a code representing the arithmetic operation that caused the exception and structures recording the operands, if they are available. It also contains a structure recording the default result that would have been substituted if the exception were not trapped and an integer value holding the bitwise "or" of the exception flags that would have accrued. The handler can modify the latter members of the structure to substitute a different result or change the set of flags that are accrued. (Note that if the handler returns without modifying these data, the program will continue with the default untrapped result and flags just as if the exception were not trapped.)As an illustration, the following section shows how to substitute a scaled result for an operation that underflows or overflows. See Appendix A for further examples.
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.)
On SPARC, when a floating point instruction incurs a trapped exception, the system leaves the destination register unchanged. Thus, in order to substitute the exponent-wrapped result, an under/overflow handler must decode the instruction, examine the operand registers, and generate the scaled result itself. The following example shows a handler that performs these steps. (In order to use this handler with code compiled for UltraSPARC systems, compile the handler on a system running Solaris 2.6, Solaris 7, or Solaris 8 and define the preprocessor token
V8PLUS
.)
CODE EXAMPLE 4-4 Substituting IEEE Trapped Under/Overflow Handler Results for SPARC Systems
#include <stdio.h> #include <ieeefp.h> #include <math.h> #include <sunmath.h> #include <siginfo.h> #include <ucontext.h> #ifdef V8PLUS /* The upper 32 floating point registers are stored in an area pointed to by uap->uc_mcontext.xrs.xrs_prt. Note that this pointer is valid ONLY when uap->uc_mcontext.xrs.xrs_id == XRS_ID (defined in sys/procfs.h). */ #include <assert.h> #include <sys/procfs.h> #define FPxreg(x) ((prxregset_t*)uap->uc_mcontext.xrs.xrs_ptr) ->pr_un.pr_v8p.pr_xfr.pr_regs[(x)] #endif #define FPreg(x) uap->uc_mcontext.fpregs.fpu_fr.fpu_regs[(x)] /* * Supply the IEEE 754 default result for trapped under/overflow */ void ieee_trapped_default(int sig, siginfo_t *sip, ucontext_t *uap) { unsigned instr, opf, rs1, rs2, rd; long double qs1, qs2, qd, qscl; double ds1, ds2, dd, dscl; float fs1, fs2, fd, fscl; /* get the instruction that caused the exception */ instr = uap->uc_mcontext.fpregs.fpu_q->FQu.fpq.fpq_instr; /* extract the opcode and source and destination register numbers */ opf = (instr >> 5) & 0x1ff; rs1 = (instr >> 14) & 0x1f; rs2 = instr & 0x1f; rd = (instr >> 25) & 0x1f; /* get the operands */ switch (opf & 3) { case 1: /* single precision */ fs1 = *(float*)&FPreg(rs1); fs2 = *(float*)&FPreg(rs2); break; case 2: /* double precision */ #ifdef V8PLUS if (rs1 & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); ds1 = *(double*)&FPxreg(rs1 & 0x1e); } else ds1 = *(double*)&FPreg(rs1); if (rs2 & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); ds2 = *(double*)&FPxreg(rs2 & 0x1e); } else ds2 = *(double*)&FPreg(rs2); #else ds1 = *(double*)&FPreg(rs1); ds2 = *(double*)&FPreg(rs2); #endif break; case 3: /* quad precision */ #ifdef V8PLUS if (rs1 & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); qs1 = *(long double*)&FPxreg(rs1 & 0x1e); } else qs1 = *(long double*)&FPreg(rs1); if (rs2 & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); qs2 = *(long double*)&FPxreg(rs2 & 0x1e); } else qs2 = *(long double*)&FPreg(rs2); #else qs1 = *(long double*)&FPreg(rs1); qs2 = *(long double*)&FPreg(rs2); #endif break; } /* set up scale factors */ if (sip->si_code == FPE_FLTOVF) { fscl = scalbnf(1.0f, -96); dscl = scalbn(1.0, -768); qscl = scalbnl(1.0, -12288); } else { fscl = scalbnf(1.0f, 96); dscl = scalbn(1.0, 768); qscl = scalbnl(1.0, 12288); } /* disable traps and generate the scaled result */ fpsetmask(0); switch (opf) { case 0x41: /* add single */ fd = fscl * (fscl * fs1 + fscl * fs2); break; case 0x42: /* add double */ dd = dscl * (dscl * ds1 + dscl * ds2); break; case 0x43: /* add quad */ qd = qscl * (qscl * qs1 + qscl * qs2); break; case 0x45: /* subtract single */ fd = fscl * (fscl * fs1 - fscl * fs2); break; case 0x46: /* subtract double */ dd = dscl * (dscl * ds1 - dscl * ds2); break; case 0x47: /* subtract quad */ qd = qscl * (qscl * qs1 - qscl * qs2); break; case 0x49: /* multiply single */ fd = (fscl * fs1) * (fscl * fs2); break; case 0x4a: /* multiply double */ dd = (dscl * ds1) * (dscl * ds2); break; case 0x4b: /* multiply quad */ qd = (qscl * qs1) * (qscl * qs2); break; case 0x4d: /* divide single */ fd = (fscl * fs1) / (fs2 / fscl); break; case 0x4e: /* divide double */ dd = (dscl * ds1) / (ds2 / dscl); break; case 0x4f: /* divide quad */ qd = (qscl * qs1) / (qs2 / dscl); break; case 0xc6: /* convert double to single */ fd = (float) (fscl * (fscl * ds1)); break; case 0xc7: /* convert quad to single */ fd = (float) (fscl * (fscl * qs1)); break; case 0xcb: /* convert quad to double */ dd = (double) (dscl * (dscl * qs1)); break; } /* store the result in the destination */ if (opf & 0x80) { /* conversion operation */ if (opf == 0xcb) { /* convert quad to double */ #ifdef V8PLUS if (rd & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); *(double*)&FPxreg(rd & 0x1e) = dd; } else *(double*)&FPreg(rd) = dd; #else *(double*)&FPreg(rd) = dd; #endif } else /* convert quad/double to single */ *(float*)&FPreg(rd) = fd; } else { /* arithmetic operation */ switch (opf & 3) { case 1: /* single precision */ *(float*)&FPreg(rd) = fd; break; case 2: /* double precision */ #ifdef V8PLUS if (rd & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); *(double*)&FPxreg(rd & 0x1e) = dd; } else *(double*)&FPreg(rd) = dd; #else *(double*)&FPreg(rd) = dd; #endif break; case 3: /* quad precision */ #ifdef V8PLUS if (rd & 1) { assert(uap->uc_mcontext.xrs.xrs_id == XRS_ID); *(long double*)&FPxreg(rd & 0x1e) = qd; } else *(long double*)&FPreg(rd & 0x1e) = qd; #else *(long double*)&FPreg(rd & 0x1e) = qd; #endif break; } } } int main() { volatile float a, b; volatile double x, y; ieee_handler("set", "underflow", ieee_trapped_default); ieee_handler("set", "overflow", ieee_trapped_default); a = b = 1.0e30f; a *= b; /* overflow; will be wrapped to a moderate number */ printf( "%g\n", a ); a /= b; printf( "%g\n", a ); a /= b; /* underflow; will wrap back */ printf( "%g\n", a ); x = y = 1.0e300; x *= y; /* overflow; will be wrapped to a moderate number */ printf( "%g\n", x ); x /= y; printf( "%g\n", x ); x /= y; /* underflow; will wrap back */ printf( "%g\n", x ); ieee_retrospective(stdout); return 0; }(In this example, the variables
a
,b
,x
, andy
have been declaredvolatile
only to prevent the compiler from evaluatinga * b
, etc., at compile time. In typical usage, thevolatile
declarations would not be needed.)The output from the preceding program is:
159.3091.59309e-2814.14884e+1374.14884e-1631Note: IEEE floating-point exception traps enabled:underflow; overflow;See the Numerical Computation Guide, ieee_handler(3M)On x86, the floating point hardware provides the exponent-wrapped result when a floating point instruction incurs a trapped underflow or overflow and its destination is a register. When trapped underflow or overflow occurs on a floating point store instruction, however, the hardware traps without completing the store (and without popping the stack, if the store instruction is a store-and-pop). Thus, in order to implement counting mode, an under/overflow handler must generate the scaled result and fix up the stack when a trap occurs on a store instruction. The following example illustrates such a handler.
CODE EXAMPLE 4-5 Substituting IEEE Trapped Under/Overflow Handler Results for x86 Systems
#include <stdio.h> #include <ieeefp.h> #include <math.h> #include <sunmath.h> #include <siginfo.h> #include <ucontext.h> /* offsets into the saved fp environment */ #define CW 0 /* control word */ #define SW 1 /* status word */ #define TW 2 /* tag word */ #define OP 4 /* opcode */ #define EA 5 /* operand address */ #define FPenv(x) uap->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[(x)] #define FPreg(x) *(long double *)(10*(x)+(char*)&uap->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[7]) /* * Supply the IEEE 754 default result for trapped under/overflow */ void ieee_trapped_default(int sig, siginfo_t *sip, ucontext_t *uap) { double dscl; float fscl; unsigned sw, op, top; int mask, e; /* preserve flags for untrapped exceptions */ sw = uap->uc_mcontext.fpregs.fp_reg_set.fpchip_state.status; FPenv(SW) |= (sw & (FPenv(CW) & 0x3f)); /* if the excepting instruction is a store, scale the stacktop, store it, and pop the stack if need be */ fpsetmask(0); op = FPenv(OP) >> 16; switch (op & 0x7f8) { case 0x110: case 0x118: case 0x150: case 0x158: case 0x190: case 0x198: fscl = scalbnf(1.0f, (sip->si_code == FPE_FLTOVF)?-96 : 96); *(float *)FPenv(EA) = (FPreg(0) * fscl) * fscl; if (op & 8) { /* pop the stack */ FPreg(0) = FPreg(1); FPreg(1) = FPreg(2); FPreg(2) = FPreg(3); FPreg(3) = FPreg(4); FPreg(4) = FPreg(5); FPreg(5) = FPreg(6); FPreg(6) = FPreg(7); top = (FPenv(SW) >> 10) & 0xe; FPenv(TW) |= (3 << top); top = (top + 2) & 0xe; FPenv(SW) = (FPenv(SW) & ~0x3800) | (top << 10); } break; case 0x510: case 0x518: case 0x550: case 0x558: case 0x590: case 0x598: dscl = scalbn(1.0, (sip->si_code == FPE_FLTOVF)?-768 : 768); *(double *)FPenv(EA) = (FPreg(0) * dscl) * dscl; if (op & 8) { /* pop the stack */ FPreg(0) = FPreg(1); FPreg(1) = FPreg(2); FPreg(2) = FPreg(3); FPreg(3) = FPreg(4); FPreg(4) = FPreg(5); FPreg(5) = FPreg(6); FPreg(6) = FPreg(7); top = (FPenv(SW) >> 10) & 0xe; FPenv(TW) |= (3 << top); top = (top + 2) & 0xe; FPenv(SW) = (FPenv(SW) & ~0x3800) | (top << 10); } break; } } int main() { volatile float a, b; volatile double x, y; ieee_handler("set", "underflow", ieee_trapped_default); ieee_handler("set", "overflow", ieee_trapped_default); a = b = 1.0e30f; a *= b; printf( "%g\n", a ); a /= b; printf( "%g\n", a ); a /= b; printf( "%g\n", a ); x = y = 1.0e300; x *= y; printf( "%g\n", x ); x /= y; printf( "%g\n", x ); x /= y; printf( "%g\n", x ); ieee_retrospective(stdout); return 0; }As on SPARC, the output from the preceding program on x86 is:
159.3091.59309e-2814.14884e+1374.14884e-1631Note: IEEE floating-point exception traps enabled:underflow; overflow;See the Numerical Computation Guide, ieee_handler(3M)C/C++ programs can use the
fex_set_handling
function inlibm9x.so
to install aFEX_CUSTOM
handler for underflow and overflow. On SPARC systems, the information supplied to such a handler always includes the operation that caused the exception and the operands, and this information is sufficient to allow the handler to compute the IEEE exponent-wrapped result, as shown above. On x86, the available information might not always indicate which particular operation caused the exception; when the exception is raised by one of the transcendental instructions, for example, theinfo->op
parameter is set tofex_other
. (See thefenv.h
file for definitions.) Moreover, the x86 hardware delivers an exponent-wrapped result automatically, and this can overwrite one of the operands if the destination of the excepting instruction is a floating point register.Fortunately, the
fex_set_handling
feature provides a simple way for a handler installed inFEX_CUSTOM
mode to substitute the IEEE exponent-wrapped result for an operation that underflows or overflows. When either of these exceptions is trapped, the handler can set
- info->res.type = fex_nodata;
to indicate that the exponent-wrapped result should be delivered. Here is an example showing such a handler:
#include <stdio.h>#include <fenv.h>void handler(int ex, fex_info_t *info) {info->res.type = fex_nodata;}int main(){volatile float a, b;volatile double x, y;fex_set_log(stderr);fex_set_handling(FEX_UNDERFLOW | FEX_OVERFLOW, FEX_CUSTOM,handler);a = b = 1.0e30f;a *= b; /* overflow; will be wrapped to a moderate number */printf("%g\n", a);a /= b;printf("%g\n", a);a /= b; /* underflow; will wrap back */printf("%g\n", a);x = y = 1.0e300;x *= y; /* overflow; will be wrapped to a moderate number */printf("%g\n", x);x /= y;printf("%g\n", x);x /= y; /* underflow; will wrap back */printf("%g\n", x);return 0;}The output from the preceding program resembles the following:
Floating point overflow at 0x00010924 main, handler: handler0x00010928 main159.3091.59309e-28Floating point underflow at 0x00010994 main, handler: handler0x00010998 main1Floating point overflow at 0x000109e4 main, handler: handler0x000109e8 main4.14884e+1374.14884e-163Floating point underflow at 0x00010a4c main, handler: handler0x00010a50 main1
Sun Microsystems, Inc. Copyright information. All rights reserved. Feedback |
Library | Contents | Previous | Next | Index |