Sun Studio 12:Fortran 编程指南

6.3 IEEE 例程

以下接口可帮助用户使用 IEEE 运算,并在手册页中进行说明。这些接口多数都在数学库 libsunmath 和几个 .h 文件中。

6.3.1 标志和 ieee_flags()

ieee_flags 函数用于查询和清除异常状态标志。它是 Sun 编译器随带的 libsunmath 库的一部分,可执行下列任务:

ieee_flags 调用的一般形式为:


      
flags = ieee_flags( action, mode, in, out )

四个参数中的每一个都是字符串。输入为 actionmodein。输出为 outflagsieee_flags 是一个整数值函数。flags 中返回有用的信息,作为 1 位标志集合。有关完整信息,请参见 ieee_flags(3m) 手册页。

下表显示了所有可能的参数值。

表 6–1 ieee_flags ( action, mode, in, out ) 参数值

参数 

允许值 

action

get, set, clear, clearall

mode

direction, exception

in, out

nearest, tozero, negative, positive, extended, double single, inexact, division, underflow, overflow, invalid all, common

注意,这些是文字字符串,且输出参数 out 必须至少是 CHARACTER*9inout 的可能值的含义取决于与其一起使用的 action 和 mode。下表对此进行了概括:

表 6–2 ieee_flags in、 out 参数的含义

inout 的值

所指 

nearest, tozero, negative, positive

舍入方向 

extended, double, single

舍入精度 

inexact, division, underflow, overflow, invalid

异常 

all

全部五种异常 

common

常见异常:无效、除法、上溢 

例如,要确定引起了标志的具有最高优先级的异常,请将输入参数 in 作为空字符串传递:


      CHARACTER *9, out
      ieeer = ieee_flags( ’get’, ’exception’, ’’, out )
      PRINT *, out, ’ flag raised’

另外,要确定是否引起了 overflow 异常标志,请将输入参数 in 设置为 overflow。返回时,如果 out 等于 overflow,会出现 overflow 异常标志;否则不会出现该标志。


      ieeer = ieee_flags( ’get’, ’exception’, ’overflow’, out )
      IF ( out.eq. ’overflow’) PRINT *,’overflow flag raised’

示例:清除 invalid 异常:


      ieeer = ieee_flags( ’clear’, ’exception’, ’invalid’, out )

示例:清除所有异常:


      ieeer = ieee_flags( ’clear’, ’exception’, ’all’, out )

示例:将舍入方向设置为零:


      ieeer = ieee_flags( ’set’, ’direction’, ’tozero’, out )

示例:将舍入精度设置为 double


      ieeer = ieee_flags( ’set’, ’precision’, ’double’, out )

6.3.1.1 用 ieee_flags 关闭所有警告消息。

使用清除 action 调用 ieee_flags(如下例所示)可以重置任何未清除的异常。在程序退出之前进行该调用,可禁止系统在程序终止时产生浮点异常警告消息。

示例:用 ieee_flags() 清除所有产生的异常:


      i = ieee_flags(’clear’, ’exception’, ’all’, out )

6.3.1.2 用 ieee_flags 检测异常

以下示例演示如何确定早期计算引起的浮点异常。会将系统 include 文件 floatingpoint.h 中定义的位屏蔽应用于 ieee_flags 的返回值。

在以下示例(即 DetExcFlg.F)中,include 文件是使用 #include 预处理程序指令引入的,这就要求以 .F 后缀命名源文件。下溢是由最小的双精度数除以 2 引起的。

示例:使用 ieee_flags 检测异常,然后对其进行解码:


#include "floatingpoint.h"
         CHARACTER*16 out
         DOUBLE PRECISION d_max_subnormal, x
         INTEGER div, flgs, inv, inx, over, under

       x = d_max_subnormal() / 2.0                ! Cause underflow

       flgs=ieee_flags(’get’,’exception’,’’,out)  ! Which are raised?

       inx   = and(rshift(flgs, fp_inexact)  , 1)  ! Decode
       div   = and(rshift(flgs, fp_division) , 1)   ! the value
       under = and(rshift(flgs, fp_underflow), 1)    ! returned
       over  = and(rshift(flgs, fp_overflow) , 1)     ! by
       inv   = and(rshift(flgs, fp_invalid)  , 1)      ! ieee_flags

       PRINT *, "Highest priority exception is: ", out
       PRINT *, ’ invalid  divide  overflo underflo inexact’
       PRINT ’(5i8)’, inv, div, over, under, inx
       PRINT *, ’(1 = exception is raised; 0 = it is not)’
       i = ieee_flags(’clear’, ’exception’, ’all’, out)    ! Clear all
       END

示例:编译并运行上述示例 (DetExcFlg.F):


demo% f95 DetExcFlg.F
demo% a.out
 Highest priority exception is: underflow
  invalid  divide  overflo underflo inexact
       0       0       0       1       1
 (1 = exception is raised; 0 = it is not)
demo%

6.3.2 IEEE 极值函数

编译器提供了一个函数集,可以调用其中的函数来返回特殊的 IEEE 极值。这些值,如 infinityminimum normal,可以直接在应用程序中使用。

示例:基于硬件支持的最小数值的收敛测试如下所示:


      IF ( delta .LE. r_min_normal() ) RETURN

下表列出了可用的值:

表 6–3 返回 IEEE 值的函数

IEEE 值 

双精度 

单精度 

infinity

d_infinity()

r_infinity()

quiet NaN

d_quiet_nan()

r_quiet_nan()

signaling NaN

d_signaling_nan()

r_signaling_nan()

min normal

d_min_normal()

r_min_normal()

min subnormal

d_min_subnormal()

r_min_subnormal()

max subnormal

d_max_subnormal()

r_max_subnormal()

max normal

d_max_normal()

r_max_normal()

两个 NaN 值(quietsignaling)是无序的,不能用于比较,如 IF(X.ne.r_quiet_nan())THEN...。要确定某些值是否是 NaN,请使用函数 ir_isnan(r)id_isnan(d)

以下手册页列出了这些函数的 Fortran 名称:

另请参见:

6.3.3 异常处理程序和 ieee_handler()

关于 IEEE 异常,通常需要关注以下问题:

用户例程的异常捕获以系统产生浮点异常信号开始。signal: floating-point exception的 UNIX 标准名称是 SIGFPE。出现异常时,SPARC 平台上的缺省情况是产生 SIGFPE。要使系统产生 SIGFPE,必须先启用异常捕获,这通常通过对 ieee_handler() 的调用来完成。

6.3.3.1 建立异常处理程序函数

要将函数作为异常处理程序建立,请将函数名称与要监视的异常的名称和要采取的操作一起传递给 ieee_handler()。一旦建立了处理程序,无论何时出现特定的浮点异常和调用指定的函数,都会产生 SIGFPE 信号。

ieee_handler() 的调用形式如下表所示:

表 6–4 ieee_handler (action , exception , handler) 的参数

参数 

类型 

可能值 

action

character

getsetclear

exception

character

invaliddivisionoverflowunderflowinexact

handler

函数名 

用户处理函数的名称或 SIGFPE_DEFAULT、SIGFPE_IGNORESIGFPE_ABORT

返回值 

integer

0 =OK

f95 编译的、调用 ieee_handler() 的 Fortran 95 例程还应该声明:

#include ’floatingpoint.h’

特殊参数 SIGFPE_DEFAULTSIGFPE_IGNORESIGFPE_ABORT 定义在这些包含文件中,可用于更改与特定异常相应的程序行为:

SIGFPE_DEFAULT SIGFPE_IGNORE

出现指定异常时不采取任何操作。 

SIGFPE_ABORT

程序在异常时中止(可能会使用转储文件)。 

6.3.3.2 编写用户异常处理程序函数

异常处理程序采取的操作由您决定。但是,此例程必须是整型函数,且具有下面指定的三个参数:

handler_name( sig, sip, uap )

示例:异常处理程序函数:


      INTEGER FUNCTION hand( sig, sip, uap )
      INTEGER sig, location
      STRUCTURE /fault/
           INTEGER address
           INTEGER trapno
      END STRUCTURE
      STRUCTURE /siginfo/
           INTEGER si_signo
           INTEGER si_code
           INTEGER si_errno
           RECORD /fault/ fault
      END STRUCTURE
      RECORD /siginfo/ sip
      location = sip.fault.address
      ... actions you take ...
      END

此示例需要修改才能运行在 64 位 SPARC 体系结构上,方法是使用 INTEGER*8 替换每个 STRUCTURE 中的所有 INTEGER 声明。

如果由 ieee_handler() 启用的处理程序例程与此示例中一样,是用 Fortran 编写的,则此例程不能对其第一个参数 (sig) 进行任何引用。该第一个参数按值传递给此例程,并且只能作为 loc(sig) 进行引用。此值是信号编号。

通过处理程序检测异常

下列示例展示如何创建处理程序例程来检测浮点异常。

示例:检测异常并中止


demo% cat DetExcHan.f
      EXTERNAL myhandler
      REAL :: r = 14.2 , s = 0.0
      i = ieee_handler (’set’, ’division’, myhandler )
      t = r/s
      END

      INTEGER FUNCTION myhandler(sig,code,context)
      INTEGER sig, code, context(5)
      CALL abort()
      END
demo% f95 DetExcHan.f
demo% a.out
Abort
demo%

SIGFPE 可在浮点异常出现的任何时间产生。检测到 SIGFPE 时,控制将传递给 myhandler 函数,该函数会立即中止。用 -g 编译,并使用 dbx 查找异常位置。

通过处理程序定位异常

示例:定位异常(打印地址)并中止:


demo% cat LocExcHan.F
#include "floatingpoint.h"
      EXTERNAL Exhandler
      INTEGER Exhandler, i, ieee_handler
      REAL:: r = 14.2 ,  s = 0.0 , t
C  Detect division by zero
      i = ieee_handler( ’set’, ’division’, Exhandler )
      t = r/s
      END

      INTEGER FUNCTION Exhandler( sig, sip, uap)
      INTEGER sig
      STRUCTURE /fault/
                  INTEGER address
      END STRUCTURE
      STRUCTURE /siginfo/
                  INTEGER si_signo
                  INTEGER si_code
                  INTEGER si_errno
                  RECORD /fault/ fault
      END STRUCTURE
      RECORD /siginfo/ sip
      WRITE (*,10)  sip.si_signo, sip.si_code, sip.fault.address
10        FORMAT(’Signal ’,i4,’ code ’,i4,’ at hex address ’, Z8 )
      Exhandler=1
      CALL abort()
      END
demo%  f95 -g LocExcHan.F
demo%  a.out
Signal   8 code    3 at hex address    11230
Abort
demo%

在 64 位 SPARC 环境中,请用 INTEGER*8 替换每个 STRUCTURE 中的 INTEGER 声明,用 i8 替换 i4 格式。(注意,该例接受 VAX Fortran STRUCTURE 语句,依靠的是 f95 编译器的扩展。)

大多数情况下,知道异常的实际地址并无太大用处,但对于 dbx 除外:


demo% dbx a.out
(dbx) stopi at 0x11230     Set breakpoint at address
(2) stopi at &MAIN+0x68
(dbx) run               Run program
Running: a.out
(process id 18803)
stopped in MAIN at 0x11230
MAIN+0x68:      fdivs   %f3, %f2, %f2
(dbx) where           Shows the line number of the exception
=>[1] MAIN(), line 7 in "LocExcHan.F"
(dbx) list 7          Displays the source code line
    7         t = r/s
(dbx) cont            Continue after breakpoint, enter handler routine
Signal    8 code    3 at hex address    11230
abort: called
signal ABRT (Abort) in _kill at 0xef6e18a4
_kill+0x8:      bgeu    _kill+0x30
Current function is exhandler
   24         CALL abort()
(dbx) quit
demo%

当然,还有更容易的方法来确定引起错误的源码行。但是,本例确实足以展示异常处理的基本内容。