4.2.6. C99 インタフェースの ARM コンパイラ拡張

ARM コンパイラでは、浮動小数点環境で実現可能なすべての機能を ARM インタフェースで実行できるように、C99 インタフェースが拡張されています。 拡張機能には、個々の例外の型のトラップの設定と解除、ユーザ定義のトラップハンドラのインストールなどが含まれます。

fenv_t 型と fexcept_t 型は、C99 では特に定義されていません。 ARM では、これら 2 つの型が同じ構造型として定義されています。

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 への変換時にオーバーフロー例外が発生した場合、その結果は単精度に丸められ、指数に 192 だけバイアスがかけられた後に、double 形式で返されます。

    • アンダーフロー例外の場合は、同じような中間結果が生成されますが、バイアス値は指数から減算されるのではなく、指数に加算されます。 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 にゼロ以外の値が設定され、切り上げられた場合、または丸めが行われなかった場合はゼロが設定されます (最後の 2 つの違いは、不正確結果ビットで示されます)。 このビットは、他のタイプの例外では無意味です。

  • 指定された exceptionINVALIDDIVBYZEROOVERFLOWUNDERFLOWINEXACT のいずれか)が発生すると、edata & FE_EX_exception にゼロ以外の値が設定されます。 このことは以下に利用できます。

    • 複数の例外タイプに同じハンドラ関数を使用する(その関数でこれらのビットをテストすることで、処理されるべき例外が識別されます)。

    • オーバーフローとアンダーフローの中間結果が丸められた値だったか、正確な値だったかを判別する。

    FE_EX_INEXACT ビットは、FE_EX_OVERFLOW または FE_EX_UNDERFLOW との組み合わせで設定できるため、不正確かどうかをテストする前にオーバーフローとアンダーフローをテストすることによって、実際に発生した例外のタイプを決定する必要があります。

  • 演算が実行されたときに FZ ビットが設定された場合は、edata & FE_EX_FLUSHZERO にゼロ以外の値が設定されます(__ieee_status()を参照)。

  • edata & FE_EX_ROUND_MASK の値は、演算に適用される丸めモードを表します。 例外を発生した演算が、必ずゼロに丸められる _ffix などのルーチンであった場合を除き、通常、このモードは現在の丸めモードと同じになります。 丸めモードの値は、FE_EX_ROUND_NEARESTFE_EX_ROUND_PLUSINFFE_EX_ROUND_MINUSINF、および FE_EX_ROUND_ZERO になる可能性があります。

  • edata & FE_EX_INTYPE_MASK は、Table 4.9 に示すいずれかの値になり、関数のオペランドの型を表します。

    Table 4.9. FE_EX_INTYPE_MASK オペランドの型フラグ

    フラグオペランドの型
    FE_EX_INTYPE_FLOATfloat
    FE_EX_INTYPE_DOUBLEdouble
    FE_EX_INTYPE_FDfloat double
    FE_EX_INTYPE_DFdouble float
    FE_EX_INTYPE_INTint
    FE_EX_INTYPE_UINTunsigned int
    FE_EX_INTYPE_LONGLONGlong long
    FE_EX_INTYPE_ULONGLONGunsigned long long
  • edata & FE_EX_OUTTYPE_MASK は、Table 4.10 に示すいずれかの値になり、関数のオペランドの型を表します。

    Table 4.10. FE_EX_OUTTYPE_MASK オペランドの型フラグ

    フラグオペランドの型
    FE_EX_OUTTYPE_FLOATfloat
    FE_EX_OUTTYPE_DOUBLEdouble
    FE_EX_OUTTYPE_INTint
    FE_EX_OUTTYPE_UINTunsigned int
    FE_EX_OUTTYPE_LONGLONGlong long
    FE_EX_OUTTYPE_ULONGLONGunsigned 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 フラグは、最初のオペランドだけの型を表します。2 番目のオペランドは常に int 型です。

    FE_EX_FN_NEXTAFTER

    次に表現可能な番号

    Note

    いずれのオペランドも同じ型です。 nexttoward の呼び出しにより、2 番目のオペランドの値が、最初のオペランドと同じ型に変更されます。 これは結果には影響しません。

    FE_EX_FN_RAISEこの例外は、feraiseexcept または feupdateenv によって明示的に生成されます。 この場合、edata のワードのほぼすべてが無効となります。

    演算が比較である場合、その結果は int として返す必要があり、Table 4.12 に示す 4 つの値のいずれかである必要があります。

    比較と変換を除くすべての演算において、入力と出力は同じ型です。

    Table 4.12. FE_EX_CMPRET_MASK の比較型フラグ

    フラグ比較
    FE_EX_CMPRET_LESSop1 < op2
    FE_EX_CMPRET_EQUALop1 = op2
    FE_EX_CMPRET_GREATERop1 > op2
    FE_EX_CMPRET_UNORDEREDop1 と op2 の比較不能
例外ハンドラの例

Example 4.1 は、ユーザ定義の例外ハンドラを示しています。 例として、Fortran コードを C 言語に変換する場合を考えます。Fortran における数値演算の標準仕様では、ゼロをゼロで除算した場合の結果が 1 になると規定されていますが、IEEE 754 では無効演算となるため、デフォルトではクワイエット型の NaN が返されます。 Fortran コードはこの動作に依存する可能性が高いので、コードを修正するよりも、ゼロをゼロで除算した場合に 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 をトラップする場合は、シグナルハンドラ関数の宣言に第 2 パラメータを含めることで、発生した浮動小数点例外の型情報を受け取るようにできます。 この機能は、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 シグナルを生成する場合は、ISO 関数 raise() の代わりに __rt_raise 関数を呼び出します。 例えば、Example 4.1 に以下のコード行がありますが、

    raise(SIGFPE);

この代わりに以下を使用します。

    __rt_raise(SIGFPE, _FPE_INVALID);

__rt_raisert_misc.h 内で定義されています。

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