Oracle® Developer Studio 12.5:数值计算指南

退出打印视图

更新时间: 2016 年 6 月
 
 

5.3 异常和异常处理

A.3.1 ieee_flags-已发生异常

通常情况下,用户程序会检查清除已发生异常位。以下示例是一个检查已发生异常标志的 C 程序。

示例 15  检查已发生异常标志
#include <sunmath.h>
#include <sys/ieeefp.h>

int main()
{
 int     code, inexact, division, underflow, overflow, invalid;
 double  x;
 char    *out;

 /* cause an underflow exception */
 x = max_subnormal() / 2.0;

 /* this statement insures that the previous */
 /* statement is not optimized away          */
 printf("x = %g\n",x);

 /* find out which exceptions are raised */
 code = ieee_flags("get", "exception", "", &out);

 /* decode the return value */
 inexact =      (code >> fp_inexact)     & 0x1;
 underflow =    (code >> fp_underflow)   & 0x1;
 division =     (code >> fp_division)    & 0x1;
 overflow =     (code >> fp_overflow)    & 0x1;
 invalid =      (code >> fp_invalid)     & 0x1;

      /* "out" is the raised exception with the highest priority */
 printf(" Highest priority exception is: %s\n", out);
 /* The value 1 means the exception is raised, */
 /* 0 means it isn't.                          */
 printf("%d %d %d %d %d\n", invalid, overflow, division,
  underflow, inexact);
 ieee_retrospective_();
 return 0;
}

运行上一程序的输出如下所示:

demo% a.out 
x = 1.11254e-308
 Highest priority exception is: underflow
0 0 0 1 1
 Note:IEEE floating-point exception flags raised:   
    Inexact;  Underflow; 
 See the Numerical Computation Guide, ieee_flags(3M)

从 Fortran 可以完成相同操作:

示例 16  检查已发生异常标志-Fortran
/*
A Fortran example that: 
    *  causes an underflow exception
    *  uses ieee_flags to determine which exceptions are raised
    *  decodes the integer value returned by ieee_flags 
    *  clears all outstanding exceptions
Remember to save this program in a file with the suffix .F, so that
the c preprocessor is invoked to bring in the header file
floatingpoint.h. 
*/
#include <floatingpoint.h> 

    program decode_accrued_exceptions
    double precision   x
    integer            accrued, inx, div, under, over, inv
    character*16       out
    double precision   d_max_subnormal
c Cause an underflow exception
    x = d_max_subnormal() / 2.0

c Find out which exceptions are raised
    accrued = ieee_flags('get', 'exception', '', out)

c Decode value returned by ieee_flags using bit-shift intrinsics
    inx   = and(rshift(accrued, fp_inexact)  , 1)
    under = and(rshift(accrued, fp_underflow), 1)
    div   = and(rshift(accrued, fp_division) , 1)
    over  = and(rshift(accrued, fp_overflow) , 1)
    inv   = and(rshift(accrued, fp_invalid)  , 1)

c The exception with the highest priority is returned in "out"
    print *, "Highest priority exception is ", out

c The value 1 means the exception is raised; 0 means it is not
    print *, inv, over, div, under, inx

c Clear all outstanding exceptions
    i = ieee_flags('clear', 'exception', 'all', out)
    end

输出如下所示:

 Highest priority exception is underflow       
   0  0  0  1  1

虽然用户程序设置异常标志是不太常见的情况,不过可以这么做。 以下 C 示例中演示了这种情况。

#include <sunmath.h>
 
int main()
{
 int     code;
 char    *out;
 
 if (ieee_flags("clear", "exception", "all", &out) != 0)
     printf("could not clear exceptions\n");
 if (ieee_flags("set", "exception", "division", &out) != 0)
     printf("could not set exception\n");
 code = ieee_flags("get", "exception", "", &out);
 printf("out is: %s , fp exception code is: %X \n",
  out, code);
 
 return 0;
}

在 SPARC 上,上一个程序的输出为:

out is: division , fp exception code is: 2 

在 x86 上,输出为:

out is: division , fp exception code is: 4

A.3.2 ieee_handler:捕获异常


注 -  以下示例仅适用于 Oracle Solaris OS。

以下 Fortran 程序安装信号处理程序以定位异常,该程序仅适用于基于 SPARC 的系统:

示例 17  在下溢时自陷 (SPARC)
  program demo
c declare signal handler function
 external fp_exc_hdl
 double precision d_min_normal
 double precision x
c set up signal handler
 i = ieee_handler('set', 'common', fp_exc_hdl)
 if (i.ne.0) print *, 'ieee trapping not supported here'
c cause an underflow exception (it will not be trapped)
 x = d_min_normal() / 13.0
 print *, 'd_min_normal() / 13.0 = ', x
c cause an overflow exception
c the value printed out is unrelated to the result
 x = 1.0d300
 x = x * x
 print *, '1.0d300*1.0d300 = ', x
 end
c
c the floating-point exception handling function
c
 integer function fp_exc_hdl(sig, sip, uap)
 integer sig, code, addr
 character label*16
c
c The structure /siginfo/ is a translation of siginfo_t
c from <sys/siginfo.h>
c
 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
c See <sys/machsig.h> for list of FPE codes
c Figure out the name of the SIGFPE
 code = sip.si_code
 if (code.eq.3) label = 'division'
 if (code.eq.4) label = 'overflow'
 if (code.eq.5) label = 'underflow'
 if (code.eq.6) label = 'inexact'
 if (code.eq.7) label = 'invalid'
 addr = sip.fault.address
c Print information about the signal that happened
 write (*,77) code, label, addr
 77  format ('floating-point exception code ', i2, ',',
     *  a17, ',', ' at address ', z8 )
 end

使用 –f77 编译上面的代码时,输出如下所示:

 d_min_normal() / 13.0 =     1.7115952757748-309
floating-point exception code  4, overflow        , at address    1131C
 1.0d300*1.0d300 =     1.0000000000000+300
 Note: 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) 

以下为 SPARC 系统上的更复杂的 C 示例:

示例 18  在无效、被零除、溢出、下溢和不精确时自陷 (SPARC)
/*
 * Generate the 5 IEEE exceptions: invalid, division,
 * overflow, underflow and inexact.
 *
 * Trap on any floating point exception, print a message,
* and continue.*
 * Note that you could also inquire about raised exceptions by
*    i = ieee("get","exception","",&out);* where out contains the name of the highest exception
 * raised, and i can be decoded to find out about all the
 * exceptions raised.
 */

#include <sunmath.h>
#include <signal.h>
#include <siginfo.h>
#include <ucontext.h>

extern void trap_all_fp_exc(int sig, siginfo_t *sip,
  ucontext_t *uap);

int main()
{
 double  x, y, z;
 char  *out;

 /*
  * Use ieee_handler to establish "trap_all_fp_exc"
  * as the signal handler to use whenever any floating
  * point exception occurs.
  */

 if (ieee_handler("set", "all", trap_all_fp_exc) != 0)
  printf(" IEEE trapping not supported here.\n");
 /* disable trapping (uninteresting) inexact exceptions */
 if (ieee_handler("set", "inexact", SIGFPE_IGNORE) != 0)
   printf("Trap handler for inexact not cleared.\n");
 /* raise invalid */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
   printf(" could not clear exceptions\n");
 printf("1. Invalid: signaling_nan(0) * 2.5\n");
 x = signaling_nan(0);
 y = 2.5;
 z = x * y;

 /* raise division */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
  printf(" could not clear exceptions\n");
 printf("2. Div0: 1.0 / 0.0\n");
 x = 1.0;
 y = 0.0;
 z = x / y;

 /* raise overflow */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
   printf(" could not clear exceptions\n");
 printf("3. Overflow: -max_normal() - 1.0e294\n");
 x = -max_normal();
 y = -1.0e294;
 z = x + y;

 /* raise underflow */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
   printf(" could not clear exceptions\n");
 printf("4. Underflow: min_normal() * min_normal()\n");
 x = min_normal();
 y = x;
 z = x * y;

 /* enable trapping on inexact exception */
 if (ieee_handler("set", "inexact", trap_all_fp_exc) != 0)
   printf("Could not set trap handler for inexact.\n");
 /* raise inexact */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
   printf(" could not clear exceptions\n");
 printf("5. Inexact: 2.0 / 3.0\n");
 x = 2.0;
 y = 3.0;
 z = x / y;
 /* don't trap on inexact */
 if (ieee_handler("set", "inexact", SIGFPE_IGNORE) != 0)
   printf(" could not reset inexact trap\n");

 /* check that we're not trapping on inexact anymore */
 if (ieee_flags("clear", "exception", "all", &out) != 0)
   printf(" could not clear exceptions\n");
 printf("6. Inexact trapping disabled; 2.0 / 3.0\n");
 x = 2.0;
 y = 3.0;
 z = x / y;

 /* find out if there are any outstanding exceptions */
 ieee_retrospective_();

 /* exit gracefully */
 return 0;
}

void trap_all_fp_exc(int sig, siginfo_t *sip, ucontext_t *uap) {
 char  *label = "undefined";

/* see /usr/include/sys/machsig.h for SIGFPE codes */
 switch (sip->si_code) {
 case FPE_FLTRES:
   label = "inexact";
   break;
 case FPE_FLTDIV:
   label = "division";
   break;
 case FPE_FLTUND:
   label = "underflow";
   break;
 case FPE_FLTINV:
   label = "invalid";
   break;
 case FPE_FLTOVF:
   label = "overflow";
   break;
 }

 printf(
 " signal %d, sigfpe code %d: %s exception at address %x\n",
   sig, sip->si_code, label, sip->__data.__fault.__addr);
}

输出以下类似内容:

1. Invalid: signaling_nan(0) * 2.5
   signal 8, sigfpe code 7: invalid exception at address 10da8
2. Div0: 1.0 / 0.0
   signal 8, sigfpe code 3: division exception at address 10e44
3. Overflow: -max_normal() - 1.0e294
   signal 8, sigfpe code 4: overflow exception at address 10ee8
4. Underflow: min_normal() * min_normal()
   signal 8, sigfpe code 5: underflow exception at address 10f80
5. Inexact: 2.0 / 3.0
   signal 8, sigfpe code 6: inexact exception at address 1106c
6. Inexact trapping disabled; 2.0 / 3.0
Note: IEEE floating-point exception traps enabled:  
   underflow; overflow; division by zero; invalid operation; 
See the Numerical Computation Guide, ieee_handler(3M)

以下代码显示了如何使用 ieee_handler 和 include 文件来修改 SPARC 上特定异常情况的缺省结果:

示例 19  修改异常情况的缺省结果
/*
 * Cause a division by zero exception and use the
 * signal handler to substitute MAXDOUBLE (or MAXFLOAT)
 * as the result.
 *
 * compile with the flag -Xa
 */
 
#include <values.h>
#include <siginfo.h>
#include <ucontext.h>

void division_handler(int sig, siginfo_t *sip, ucontext_t *uap);

int main() {
 double     x, y, z;
 float     r, s, t;
 char     *out;

 /*
  * Use ieee_handler to establish division_handler as the
  * signal handler to use for the IEEE exception division.
  */
 if (ieee_handler("set","division",division_handler)!=0) {
  printf(" IEEE trapping not supported here.\n");
 }

 /* Cause a division-by-zero exception */
 x = 1.0;
 y = 0.0;
 z = x / y;

 /*
  * Check to see that the user-supplied value, MAXDOUBLE,
  * is indeed substituted in place of the IEEE default
  * value, infinity.
  */
 printf("double precision division: %g/%g = %g \n",x,y,z);

 /* Cause a division-by-zero exception */
 r = 1.0;
 s = 0.0;
 t = r / s;

 /*
  * Check to see that the user-supplied value, MAXFLOAT,
  * is indeed substituted in place of the IEEE default
  * value, infinity.
  */
 printf("single precision division: %g/%g = %g \n",r,s,t);

 ieee_retrospective_();

 return 0;
}

void division_handler(int sig, siginfo_t *sip, ucontext_t *uap) {
 int       inst;
 unsigned       rd, mask, single_prec=0;
 float       f_val = MAXFLOAT;
 double       d_val = MAXDOUBLE;
 long       *f_val_p = (long *) &f_val;

 /* Get instruction that caused exception. */
 inst = uap->uc_mcontext.fpregs.fpu_q->FQu.fpq.fpq_instr;

 /*
  * Decode the destination register. Bits 29:25 encode the
  * destination register for any SPARC floating point
  * instruction.
  */
 mask = 0x1f;
 rd = (mask & (inst >> 25));

 /*
  * Is this a single precision or double precision
  * instruction?  Bits 5:6 encode the precision of the
  * opcode; if bit 5 is 1, it's sp, else, dp.
  */

 mask = 0x1;
 single_prec = (mask & (inst >> 5));

 /* put user-defined value into destination register */
 if (single_prec) {
  uap->uc_mcontext.fpregs.fpu_fr.fpu_regs[rd] =
   f_val_p[0];
 } else {
 uap->uc_mcontext.fpregs.fpu_fr.fpu_dregs[rd/2] = d_val;
 }
}

以下为预期的输出:

double precision division: 1/0 = 1.79769e+308 
single precision division: 1/0 = 3.40282e+38 
Note: IEEE floating-point exception traps enabled: 
   division by zero; 
See the Numerical Computation Guide, ieee_handler(3M)

A.3.3 ieee_handler:出现异常时中止

可以使用 ieee_handler 来强制程序在出现特定浮点异常时中止:

#include <floatingpoint.h>
 program abort
c
 ieeer = ieee_handler('set', 'division', SIGFPE_ABORT)
 if (ieeer .ne. 0) print *, ' ieee trapping not supported'
 r = 14.2
 s = 0.0
 r = r/s
c
 print *, 'you should not see this; system should abort'
c
 end 

A.3.4 libm 异常处理功能

下面的示例显示了如何使用 libm 中提供的一些异常处理功能。第一个示例基于以下任务:提供数字 x 和系数 a0a1、...、aN,以及 b0b1、...、bN-1,对函数 f(x) 及其一阶导数 f'(x) 求值,其中 f() 是连分数:

f(x) =a0 + b0 / (x + a1 + b1 / (x + .../ (x + aN-1 + bN-1 / (x + aN))...))。

在 IEEE 运算中直接计算 f():即使其中一个中间除法溢出或者被零除,该标准指定的缺省值(带正确符号的无穷大)也会得到正确结果。另一方面,计算 f'() 可能会更困难,因为对其最简单的求值方法具有可去奇点。如果计算过程遇到这些奇点之一,则会尝试对不定式 0/0、0*无穷大或无穷大/无穷大之一求值,这些不定式会引发无效运算异常。W. Kahan 建议了一种方法,使用称为预替换的功能来处理这些异常。

预替换是对 IEEE 缺省异常响应的扩展,允许用户预先指定在异常运算中要替换的结果值。在 libm 中使用异常处理工具,程序可以通过在 FEX_CUSTOM 异常处理模式下安装处理程序,方便地实现预替换功能。使用此模式时,处理程序只需将值存储在数据结构中,传递到处理程序的信息参数指向该数据结构,这样就能为异常运算的结果提供任意值。以下样例程序使用通过 FEX_CUSTOM 处理程序实现的预替换功能来计算连分数及其导数。

示例 20  使用 FEX_CUSTOM 处理程序计算连分数及其导数
#include <stdio.h>
#include <sunmath.h>
#include <fenv.h>
volatile double p;
void handler(int ex, fex_info_t *info)
{
    info->res.type = fex_double;
    if (ex == FEX_INV_ZMI)
        info->res.val.d = p;
    else
        info->res.val.d = infinity();
}

/*
*  Evaluate the continued fraction given by coefficients a[j] and
*  b[j] at the point x; return the function value in *pf and the
*  derivative in *pf1
*/
void continued_fraction(int N, double *a, double *b,
    double x, double *pf, double *pf1)
{
    fex_handler_t    oldhdl; /* for saving/restoring handlers */
    volatile double  t;
    double           f, f1, d, d1, q;
    int              j;

    fex_getexcepthandler(&oldhdl, FEX_DIVBYZERO | FEX_INVALID);

    fex_set_handling(FEX_DIVBYZERO, FEX_NONSTOP, NULL);
    fex_set_handling(FEX_INV_ZDZ | FEX_INV_IDI | FEX_INV_ZMI,
        FEX_CUSTOM, handler);

    f1 = 0.0;
    f = a[N];
    for (j = N - 1; j >= 0; j--) {
        d = x + f;
        d1 = 1.0 + f1;
        q = b[j] / d;
        /* the following assignment to the volatile variable t
           is needed to maintain the correct sequencing between
           assignments to p and evaluation of f1 */
        t = f1 = (-d1 / d) * q;
        p = b[j-1] * d1 / b[j];
        f = a[j] + q;
    }

    fex_setexcepthandler(&oldhdl, FEX_DIVBYZERO | FEX_INVALID);

    *pf = f;
    *pf1 = f1;
}

/* For the following coefficients, x = -3, 1, 4, and 5 will all
   encounter intermediate exceptions */
double a[] = { -1.0, 2.0, -3.0, 4.0, -5.0 };
double b[] = { 2.0, 4.0, 6.0, 8.0 };

int main()
{
    double  x, f, f1;
    int     i;

    feraiseexcept(FE_INEXACT); /* prevent logging of inexact */
    fex_set_log(stdout);
    fex_set_handling(FEX_COMMON, FEX_ABORT, NULL);
    for (i = -5; i <= 5; i++) {
        x = i;
        continued_fraction(4, a, b, x, &f, &f1);
        printf("f(% g) = %12g, f'(% g) = %12g\n", x, f, x, f1);
    }
    return 0;
}

多个有关程序的注释是按顺序排列的。在进入时,函数 continued_fraction 保存当前针对被零除和所有无效运算异常的异常处理模式。然后,它为被零除建立连续异常处理,并且为三个不定式建立 FEX_CUSTOM 处理程序。此处理程序会将 0/0 以及无穷大/无穷大替换为无穷大,而将 0*无穷大替换为全局变量 p 的值。请注意,在每次执行函数求值循环时都必须重新计算 p,以提供正确值来替换后续的 0*无穷大这一无效运算。另请注意,p 必须声明为 volatile 以防止编译器删除它,因为它在循环中的其他位置未显式涉及。最后,对于使用 p 来提供预替换值的计算过程,为了防止编译器将 p 的赋值移动到该计算过程之上或之下(这会导致异常),该计算过程的结果还将赋值给 volatile 变量(程序中的变量名为 t)。对 fex_setexcepthandler 的最终调用将恢复被零除和无效运算的原始处理模式。

主程序通过调用 fex_set_log 函数来启用对追溯诊断的日志记录。执行操作之前,它会引发不精确标志;这具有防止记录不精确异常的效果。请记住,在 FEX_NONSTOP 模式中,如果引发了其标志,则不记录异常;如回顾诊断一节中所述。主程序还会为常见异常建立 FEX_ABORT 模式,以确保任何未由 continued_fraction 处理的不常见异常将导致程序终止。最后,程序在多个不同点对特定连分数求值。如以下样例输出中所示,计算过程并未真正遇到中间异常:

f(-5) =     -1.59649,   f'(-5) =      -0.1818
f(-4) =     -1.87302,   f'(-4) =    -0.428193
Floating point division by zero at 0x08048dbe continued_fraction, nonstop mode
  0x08048dc1  continued_fraction
  0x08048eda  main
Floating point invalid operation (inf/inf) at 0x08048dcf continued_fraction, handler: handler
  0x08048dd2  continued_fraction
  0x08048eda  main
Floating point invalid operation (0*inf) at 0x08048dd2 continued_fraction, handler: handler
  0x08048dd8  continued_fraction
  0x08048eda  main
f(-3) =           -3,   f'(-3) =     -3.16667
f(-2) = -4.44089e-16,   f'(-2) =     -3.41667
f(-1) =     -1.22222,   f'(-1) =    -0.444444
f( 0) =     -1.33333,   f'( 0) =     0.203704
f( 1) =           -1,   f'( 1) =     0.333333
f( 2) =    -0.777778,   f'( 2) =      0.12037
f( 3) =    -0.714286,   f'( 3) =    0.0272109
f( 4) =    -0.666667,   f'( 4) =     0.203704
f( 5) =    -0.777778,   f'( 5) =    0.0185185

f'(x) 计算中,出现在 x = 1、4 和 5 时的异常不会生成追溯诊断消息,因为它们在程序中出现的位置与 x = –3 时出现的异常位置相同。

以上程序可能不是处理在连分数及其导数求值过程中出现的异常的最有效方法。原因之一是每次迭代循环时都必须重新计算预替换值,不论是否需要。在这种情况下,预替换值的计算涉及到浮点除法,在现代的 SPARC 和 x86 处理器上,浮点除法是相对较慢的运算。此外,循环本身涉及到两个除法,由于大部分 SPARC 和 x86 处理器无法交叠执行两个不同的除法运算,除法很可能成为循环中的瓶颈;添加另一个除法还会加剧这一瓶颈。

可以重新编写循环,这样只需要一次除法,特别是在预替换值的计算不需要涉及到除法的情况下。要以这种方式重新编写循环,用户必须预先计算 b 数组中系数的相邻元素比率。这将消除多次除法运算的瓶颈,但是不会消除预替换值计算中涉及的所有算术运算。此外,需要对预替换值进行赋值以及对预替换为 volatile 变量的运算结果进行赋值,这会带来额外的内存操作,从而降低程序运行速度。虽然对于防止编译器重新排序特定关键运算而言,这些赋值是必要的,但是也会有效地阻止编译器重新排序其他无关运算。因此,在本例中通过预替换处理异常需要额外的内存运算,并会预先排除一些在其他情况下可能可用的优化。能否更高效地处理这些异常?

在缺少特定硬件支持来实现快速预替换的情况下,处理本例中异常的最有效方法是使用标志,如以下版本所示:

示例 21  使用标志处理异常(续)
#include <stdio.h>
#include <math.h>
#include <fenv.h>

/*
*  Evaluate the continued fraction given by coefficients a[j] and
*  b[j] at the point x; return the function value in *pf and the
*  derivative in *pf1
*/
void continued_fraction(int N, double *a, double *b,
    double         x, double *pf, double *pf1)
{
    fex_handler_t  oldhdl;
    fexcept_t      oldinvflag;
    double         f, f1, d, d1, pd1, q;
    int            j;

    fex_getexcepthandler(&oldhdl, FEX_DIVBYZERO | FEX_INVALID);
    fegetexceptflag(&oldinvflag, FE_INVALID);

    fex_set_handling(FEX_DIVBYZERO | FEX_INV_ZDZ | FEX_INV_IDI |
        FEX_INV_ZMI, FEX_NONSTOP, NULL);
    feclearexcept(FE_INVALID);

    f1 = 0.0;
    f = a[N];
    for (j = N - 1; j >= 0; j--) {
        d = x + f;
        d1 = 1.0 + f1;
        q = b[j] / d;
        f1 = (-d1 / d) * q;
        f = a[j] + q;
    }

    if (fetestexcept(FE_INVALID)) {
        /* recompute and test for NaN */
        f1 = pd1 = 0.0;
        f = a[N];
        for (j = N - 1; j >= 0; j--) {
            d = x + f;
            d1 = 1.0 + f1;
            q = b[j] / d;
            f1 = (-d1 / d) * q;
            if (isnan(f1))
                f1 = b[j] * pd1 / b[j+1];
            pd1 = d1;
            f = a[j] + q;
        }
    }

    fesetexceptflag(&oldinvflag, FE_INVALID);
    fex_setexcepthandler(&oldhdl, FEX_DIVBYZERO | FEX_INVALID);

    *pf = f;
    *pf1 = f1;
}

在此版本中,第一个循环尝试以缺省的连续模式计算 f(x)f'(x)。如果引发了无效标志,则第二个循环将重新计算 f(x)f'(x),显式测试是否出现 NaN。通常,不会出现无效运算异常,因此程序仅执行第一个循环。此循环不引用 volatile 变量,也没有额外的算术运算,因此运行速度将实现编译器所能达到的最大速度。实现这种效率的代价是需要编写与第一个循环几乎相同的第二个循环,用于处理出现异常的情况。这一折衷是浮点异常处理所带来的典型难题。

A.3.5 在 Fortran 程序中使用 libm 异常处理

libm 中的异常处理工具主要在 C/C++ 程序中使用,不过利用 Sun Fortran 语言的互操作性功能,您也可以从 Fortran 程序调用某些 libm 函数。


注 - 如果需要一致的行为,请不要在同一个程序中同时使用 libm 异常处理函数和 ieee_flags 以及 ieee_handler 函数。

以下示例显示了使用预替换来对连分数及其导数进行求值的 Fortran 程序版本(仅限 SPARC):

示例 22  使用预替换对连分数及其导数进行求值 (SPARC)
c
c Presubstitution handler
c
 subroutine handler(ex, info)
 structure /fex_numeric_t/
 integer type
 union
 map
 integer i
 end map
 map
 integer*8 l
 end map
 map
 real f
 end map
 map
 real*8 d
 end map
 map
 real*16 q
 end map
 end union
 end structure
 
 structure /fex_info_t/
 integer op, flags
 record /fex_numeric_t/ op1, op2, res
 end structure
 integer ex
 record /fex_info_t/ info
 common /presub/ p
 double precision p, d_infinity
 volatile p
c 4 = fex_double; see <fenv.h> for this and other constants
 info.res.type = 4
c x'80' = FEX_INV_ZMI
 if (loc(ex) .eq. x'80') then
 info.res.d = p
 else
 info.res.d = d_infinity()
 endif
 return
 end
c
c Evaluate the continued fraction given by coefficients a(j) and
c b(j) at the point x; return the function value in f and the
c derivative in f1
c
 subroutine continued_fraction(n, a, b, x, f, f1)
 integer n
 double precision a(*), b(*), x, f, f1
 common /presub/ p
 integer j, oldhdl
 dimension oldhdl(24)
 double precision d, d1, q, p, t
 volatile p, t
 data ixff2/x'ff2'/
 data ix2/x'2'/
 data ixb0/x'b0'/
 
 external fex_getexcepthandler, fex_setexcepthandler
 external fex_set_handling, handler
c$pragma c(fex_getexcepthandler, fex_setexcepthandler)
c$pragma c(fex_set_handling)
c x'ff2' = FEX_DIVBYZERO | FEX_INVALID
 call fex_getexcepthandler(oldhdl, %val(ixff2))
c x'2' = FEX_DIVBYZERO, 0 = FEX_NONSTOP
 call fex_set_handling(%val(ix2), %val(0), %val(0))
c x'b0' = FEX_INV_ZDZ | FEX_INV_IDI | FEX_INV_ZMI, 3 = FEX_CUSTOM
 call fex_set_handling(%val(ixb0), %val(3), handler)
 f1 = 0.0d0
 f = a(n+1)
 do j = n, 1, -1
 d = x + f
 d1 = 1.0d0 + f1
 q = b(j) / d
 f1 = (-d1 / d) * q
c
c the following assignment to the volatile variable t
c is needed to maintain the correct sequencing between
c assignments to p and evaluation of f1
 t = f1
 p = b(j-1) * d1 / b(j)
 f = a(j) + q
 end do
 call fex_setexcepthandler(oldhdl, %val(ixff2))
 return
 end
 
c Main program
c
 program cf
 integer i
 double precision a, b, x, f, f1
 dimension a(5), b(4)
 data a /-1.0d0, 2.0d0, -3.0d0, 4.0d0, -5.0d0/
 data b /2.0d0, 4.0d0, 6.0d0, 8.0d0/
 data ixffa/x'ffa'/

 external fex_set_handling
c$pragma c(fex_set_handling)
c x'ffa' = FEX_COMMON, 1 = FEX_ABORT
 call fex_set_handling(%val(ixffa), %val(1), %val(0))
 do i = -5, 5
 x = dble(i)
 call continued_fraction(4, a, b, x, f, f1)
 write (*, 1) i, f, i, f1
 end do
 1  format('f(', I2, ') = ', G12.6, ', f''(', I2, ') = ', G12.6)
 end
 

此程序使用 –f77 标志编译的输出如下所示:

f(-5) = -1.59649    , f'(-5) = -.181800
f(-4) = -1.87302    , f'(-4) = -.428193
f(-3) = -3.00000    , f'(-3) = -3.16667
f(-2) = -.444089E-15, f'(-2) = -3.41667
f(-1) = -1.22222    , f'(-1) = -.444444
f( 0) = -1.33333    , f'( 0) = 0.203704
f( 1) = -1.00000    , f'( 1) = 0.333333
f( 2) = -.777778    , f'( 2) = 0.120370
f( 3) = -.714286    , f'( 3) = 0.272109E-01
f( 4) = -.666667    , f'( 4) = 0.203704
f( 5) = -.777778    , f'( 5) = 0.185185E-01
 Note: IEEE floating-point exception flags raised:
    Inexact;  Division by Zero;  Underflow; Invalid Operation;
 IEEE floating-point exception traps enabled:
    overflow;  division by zero;  invalid operation;
 See the Numerical Computation Guide, ieee_flags(3M),
ieee_handler(3M)