4.2.6. ARM 编译器的 C99 接口扩展

ARM 编译器为 C99 接口提供了一些扩展,以使其能够执行 ARM 浮点环境允许的所有操作。 这包括捕获和不捕获个别异常类型,并且还包括安装自定义捕获处理程序。

C99 没有将 fenv_tfexcept_t 类型定义为特殊类型。 ARM 编译器将它们定义为相同的结构类型:

typedef struct

{

    unsigned statusword;

    __ieee_handler_t __invalid_handler;

    __ieee_handler_t __divbyzero_handler;

    __ieee_handler_t __overflow_handler;

    __ieee_handler_t __underflow_handler;

    __ieee_handler_t __inexact_handler;

} fenv_t, fexcept_t;

此结构的成员是:

编写自定义异常捕获处理程序

如果要安装自定义异常捕获处理程序,请将其声明为函数,如下所示:


__softfp__ieee_value_t myhandler(__ieee_value_t op1,

                                 __ieee_value_t op2,

                                 __ieee_edata_t edata);

此函数的参数是:

  • op1op2 用于为导致出现异常的运算指定操作数或中间结果:

    • 对于“无效运算”和“除以零”异常,将提供原始操作数。

    • 对于“不精确结果”异常,仅提供以任何方式返回的普通结果。 这是在 op1 中提供的。

    • 对于“溢出”异常,将提供中间结果。 此结果的计算方法是,计算在指数范围足够大时的运算返回值,然后调整指数以使其符合格式的要求。 在单精度型中,将指数调整 192 (0xC0);在双精度型中,将指数调整 1536 (0x600)。

      如果在将 double 转换为 float 时发生“溢出”,则以 double 格式提供结果、将其舍入为单精度型并且指数偏移 192。

    • 对于“下溢”异常,将生成类似的中间结果,但将偏移值加到指数上,而不是从指数中减去。 edata 参数也包含一个标记,用于说明需要向上舍入、向下舍入还是根本不舍入中间结果。

    __ieee_value_t 类型被定义为可传递操作数的所有可能类型的联合,如下所示:

    typedef union
    
    {
    
        float __f;
    
        float __s;
    
        double __d;
    
        int __i;
    
        unsigned int __ui;
    
    #if !defined(__STRICT_ANSI__) || (defined(__STDC_VERSION__) && 199901L <= __STDC_VERSION__)
    
        long long __l;
    
        unsigned long long __ul;
    
    #endif                                 /* __STRICT_ANSI__ */
    
        struct { int __word1, __word2; } __str;
    
    } __ieee_value_t;                      /* in/out values passed to traps */
    
    

    Note

    如果未使用 --strict 进行编译,并且代码使用旧 __ieee_value_t 定义,则旧代码仍然有效。 有关详细信息,请参阅 fenv.h 文件。

  • edata 包含一些标记,它们提供了有关所发生的异常以及执行的运算的详细信息。 (__ieee_edata_t 类型是 unsigned int 的同义词。)

  • 函数的返回值用作导致异常的运算的结果。

包含在 edata 中的标记是:

  • edata & FE_EX_RDIR 在“下溢”的中间结果向下舍入时不为零,在向上舍入或未舍入时为零。 (“不精确结果”位中给出了后两种情况之间的差值。) 对于任何其他异常类型,此位没有意义。

  • 如果发生了给定异常INVALIDDIVBYZEROOVERFLOWUNDERFLOWINEXACT),则 edata & FE_EX_exception 不为零。 这样,即可:

    • 将相同的处理函数用于多种异常类型(函数可测试这些位以指出应该处理哪种异常)

    • 确定“溢出”和“下溢”中间结果是已舍入的结果还是精确结果。

    由于可以将 FE_EX_INEXACT 位与 FE_EX_OVERFLOWFE_EX_UNDERFLOW 一起进行设置,因此,必须在测试“不精确”结果之前测试“溢出”和“下溢”以确定实际发生的异常类型。

  • 如果在执行运算时设置了 FZ 位,则 edata & FE_EX_FLUSHZERO 不为零(请参阅__ieee_status())。

  • edata & FE_EX_ROUND_MASK 给出了应用于运算的舍入模式。 这通常与当前舍入模式相同,除非导致异常的运算是始终向零舍入的例程,如 _ffix。 可用的舍入模式值是 FE_EX_ROUND_NEARESTFE_EX_ROUND_PLUSINFFE_EX_ROUND_MINUSINFFE_EX_ROUND_ZERO

  • edata & FE_EX_INTYPE_MASK 给出了函数操作数的类型,如Table 4.9 中显示的类型之一。

    Table 4.9. FE_EX_INTYPE_MASK 操作数类型标记

    标记 操作数类型
    FE_EX_INTYPE_FLOAT float
    FE_EX_INTYPE_DOUBLE double
    FE_EX_INTYPE_FD float double
    FE_EX_INTYPE_DF double float
    FE_EX_INTYPE_INT int
    FE_EX_INTYPE_UINT unsigned int
    FE_EX_INTYPE_LONGLONG long long
    FE_EX_INTYPE_ULONGLONG unsigned long long
  • edata & FE_EX_OUTTYPE_MASK 给出了函数操作数的类型,如Table 4.10 中显示的类型之一。

    Table 4.10. FE_EX_OUTTYPE_MASK 操作数类型标记

    标记 操作数类型
    FE_EX_OUTTYPE_FLOAT float
    FE_EX_OUTTYPE_DOUBLE double
    FE_EX_OUTTYPE_INT int
    FE_EX_OUTTYPE_UINT unsigned int
    FE_EX_OUTTYPE_LONGLONG long long
    FE_EX_OUTTYPE_ULONGLONG unsigned long long
  • edata & FE_EX_FN_MASK 给出了导致异常的运算的特性,如Table 4.11 中显示的运算代码之一。

    Table 4.11. FE_EX_FN_MASK 运算类型标记

    标记 运算类型
    FE_EX_FN_ADD 相加。
    FE_EX_FN_SUB 相减。
    FE_EX_FN_MUL 相乘。
    FE_EX_FN_DIV 相除。
    FE_EX_FN_REM 求余数。
    FE_EX_FN_RND 取整。
    FE_EX_FN_SQRT 求平方根。
    FE_EX_FN_CMP 比较。
    FE_EX_FN_CVT 格式转换。
    FE_EX_FN_LOGB 指数获取。
    FE_EX_FN_SCALBN

    定标。

    Note

    FE_EX_INTYPE_MASK 标记仅指定第一个操作数的类型,第二个操作数始终是 int

    FE_EX_FN_NEXTAFTER

    下一个可表示的数。

    Note

    两个操作数的类型相同。 nexttoward 调用可导致将第二个操作数的值更改为与第一个操作数相同类型的值。 这不会影响结果。

    FE_EX_FN_RAISE feraiseexceptfeupdateenv 显式地产生异常。 在这种情况下,edata 字中的所有信息几乎都是无效的。

    如果是比较运算,则必须以 int 方式返回结果,而且必须为Table 4.12 中显示的四个值之一。

    对于“比较”和“转换”之外的所有运算,输入和输出类型是相同的。

    Table 4.12. FE_EX_CMPRET_MASK 比较类型标记

    标记 比较
    FE_EX_CMPRET_LESS op1 小于 op2
    FE_EX_CMPRET_EQUAL op1 等于 op2
    FE_EX_CMPRET_GREATER op1 大于 op2
    FE_EX_CMPRET_UNORDERED 无法比较 op1 和 op2
示例异常处理程序

Example 4.1 说明了一个自定义异常处理程序。 假定将某些 Fortran 代码转换为 C 代码。Fortran 数值标准要求 0 除以 0 得 1,而 IEEE 754 规定 0 除以 0 是“无效运算”,因此,缺省返回无提示 NaN。 Fortran 代码可能会依赖于这种行为,让 0 除以 0 返回 1 可能更方便一些,而不是对代码进行修改。

进行编译时,必须选择一种支持异常的浮点模型,例如 --fpmode=ieee_full--fpmode=ieee_fixed

安装处理程序之后,0.0 除以 0.0 返回 1.0。

Example 4.1.  自定义异常处理程序


#include <fenv.h>

#include <signal.h>

#include <stdio.h>



__softfp __ieee_value_t myhandler(__ieee_value_t op1, __ieee_value_t op2,

                                 __ieee_edata_t edata)

{

    __ieee_value_t ret;

    if ((edata & FE_EX_FN_MASK) == FE_EX_FN_DIV)

    {

        if ((edata & FE_EX_INTYPE_MASK) == FE_EX_INTYPE_FLOAT) 


        {

            if (op1.f == 0.0 && op2.f == 0.0)

            {

                ret.f = 1.0;

                return ret;

            }

        } 

        if ((edata & FE_EX_INTYPE_MASK) == FE_EX_INTYPE_DOUBLE)

        {

            if (op1.d == 0.0 && op2.d == 0.0)

            {

                ret.d = 1.0;

                return ret;

            }

        }

    }

    /* For all other invalid operations, raise SIGFPE as usual */

    raise(SIGFPE);

}



int main(void)

{

    float i, j, k;

    fenv_t env;



    fegetenv(&env);

    env.statusword |= FE_IEEE_MASK_INVALID;

    env.invalid_handler = myhandler;

    fesetenv(&env);



    i = 0.0;

    j = 0.0;

    k = i/j;

    printf(“k is %f\nì, k);

}

通过信号处理异常捕获

如果捕获了某种异常,但将捕获处理程序地址设置为 NULL,则使用缺省捕获处理程序。

缺省捕获处理程序发出 SIGFPE 信号。 SIGFPE 的缺省处理程序输出一条错误消息并终止程序。

如果捕获 SIGFPE,则可以声明信号处理函数,以使用第二个参数指示所发生的浮点异常类型。 此功能是为了与 Microsoft 产品保持兼容而提供的。 值为 _FPE_INVALID_FPE_ZERODIVIDE_FPE_OVERFLOW_FPE_UNDERFLOW_FPE_INEXACT。 这些值是在 float.h 中定义的。 例如:

void sigfpe(int sig, int etype)

{

    printf("SIGFPE (%s)\n",

           etype == _FPE_INVALID ? "Invalid Operation" :

           etype == _FPE_ZERODIVIDE ? "Divide by Zero" :

           etype == _FPE_OVERFLOW ? "Overflow" :

           etype == _FPE_UNDERFLOW ? "Underflow" :

           etype == _FPE_INEXACT ? "Inexact Result" :

           "Unknown");

}

signal(SIGFPE, (void(*)(int))sigfpe);

要使用此额外信息生成您自己的 SIGFPE 信号,可以调用 __rt_raise 函数以替代 ISO 函数 raise()。 在Example 4.1 中,替代的是:


    raise(SIGFPE);

最好使用以下方式进行编码:


    __rt_raise(SIGFPE, _FPE_INVALID);

rt_misc.h 中声明 __rt_raise

Copyright © 2007 ARM Limited. All rights reserved. ARM DUI 0349AC
Non-Confidential