C標準ライブラリ<fenv.h>

C標準ライブラリ<fenv.h>

<fenv.h>はC標準ライブラリのヘッダファイルで、浮動小数点環境(Floating-Point Environment)を制御するために使用されます。

<fenv.h>C99標準で導入され、浮動小数点例外、丸めモード、その他の浮動小数点状態の制御および問い合わせ機能を提供します。


<fenv.h>の主な目的は次のとおりです:

  • 浮動小数点例外(ゼロ除算、オーバーフローなど)の検出と処理。

  • 浮動小数点演算の丸めモード(ゼロ方向への丸め、最近接丸めなど)の制御。

  • 浮動小数点状態フラグの問い合わせと変更。


1、浮動小数点例外

浮動小数点例外とは、浮動小数点演算で発生する特別な状況を指します。例えば:

  • FE_DIVBYZERO:ゼロ除算。

  • FE_INEXACT:不正確な結果。

  • FE_INVALID:無効な操作(負数の平方根など)。

  • FE_OVERFLOW:オーバーフロー。

  • FE_UNDERFLOW:アンダーフロー。

これらの例外は浮動小数点状態フラグによって表され、<fenv.h>の関数で検出および処理できます。


2、丸めモード

丸めモードは、浮動小数点演算結果の丸め方を制御します。<fenv.h>は以下の丸めモードを定義しています:

  • FE_TONEAREST:最も近い値への丸め(デフォルトモード)。

  • FE_DOWNWARD:負の無限大方向への丸め。

  • FE_UPWARD:正の無限大方向への丸め。

  • FE_TOWARDZERO:ゼロ方向への丸め。


3、主な関数とマクロ

<fenv.h>は、浮動小数点環境の操作と浮動小数点例外の処理に使用される一連の関数とマクロを提供します。

浮動小数点例外処理

関数/マクロ 説明
feclearexcept(int excepts) 指定された浮動小数点例外フラグをクリアする
feraiseexcept(int excepts) 指定された浮動小数点例外を発生させる
fetestexcept(int excepts) 指定された浮動小数点例外が発生したかどうかをテストする
fegetexceptflag(fexcept_t *flagp, int excepts) 浮動小数点例外フラグの状態を取得する
fesetexceptflag(const fexcept_t *flagp, int excepts) 浮動小数点例外フラグの状態を設定する

丸めモード制御

関数/マクロ 説明
fegetround(void) 現在の丸めモードを取得する
fesetround(int round) 現在の丸めモードを設定する

浮動小数点環境制御

関数/マクロ 説明
fegetenv(fenv_t *envp) 現在の浮動小数点環境を保存する
fesetenv(const fenv_t *envp) 浮動小数点環境を復元する
feholdexcept(fenv_t *envp) 現在の浮動小数点環境を保存し、例外フラグをクリアする
feupdateenv(const fenv_t *envp) 浮動小数点環境を復元し、例外を発生させる

4、例

以下は、<fenv.h>を使用した例で、浮動小数点例外の検出方法と丸めモードの制御方法を示しています:

例

#include <stdio.h>
#include <fenv.h>
#include <math.h>

int main() {
    // 浮動小数点例外検出を有効にする
    feclearexcept(FE_ALL_EXCEPT);

    // ゼロ除算例外を発生させる
    double x = 1.0, y = 0.0;
    double z = x / y;

    // ゼロ除算例外が発生したかどうかを検出する
    if (fetestexcept(FE_DIVBYZERO)) {
        printf("Divide by zero exception occurred.\n");
    }

    // 丸めモードをゼロ方向への丸めに設定する
    fesetround(FE_TOWARDZERO);

    // 丸めモードをテストする
    double a = 1.5;
    double b = rint(a); // 整数に丸める
    printf("Rounding 1.5 toward zero: %.1f\n", b);

    return 0;
}

出力結果は次のとおりです:

Divide by zero exception occurred.
Rounding 1.5 toward zero: 1.0

浮動小数点例外の検出

例

#include <fenv.h>
#include <stdio.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON

void check_exceptions() {
    // すべての例外フラグをクリアする
    feclearexcept(FE_ALL_EXCEPT);
   
    // 例外を引き起こす可能性のある演算を実行する
    double x = 0.0;
    double y = 1.0 / x;  // ゼロ除算
   
    // 特定の例外をチェックする
    if (fetestexcept(FE_DIVBYZERO)) {
        printf("Division by zero occurred!\n");
    }
   
    // 任意の例外をチェックする
    if (fetestexcept(FE_ALL_EXCEPT)) {
        printf("At least one floating-point exception occurred\n");
    }
}

int main() {
    check_exceptions();
    return 0;
}

丸め方向の制御

例

#include <fenv.h>
#include <stdio.h>

#pragma STDC FENV_ACCESS ON

void test_rounding(double num) {
    int modes[] = {FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, FE_TOWARDZERO};
    const char *names[] = {"Nearest", "Up", "Down", "Toward zero"};
   
    for (int i = 0; i < 4; i++) {
        if (fesetround(modes[i]) == 0) {
            printf("Rounding %s: %.1f -> %.0f\n",
                  names[i], num, rint(num));
        }
    }
}

int main() {
    test_rounding(2.5);  // 2.5のさまざまな丸めモードでの結果をテストする
    test_rounding(-2.5); // -2.5のさまざまな丸めモードでの結果をテストする
    return 0;
}

浮動小数点環境の保存と復元

例

#include <fenv.h>
#include <stdio.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON

void sensitive_calculation() {
    fenv_t env;
   
    // 現在の環境を保存する
    fegetenv(&env);
   
    // 厳密な環境を設定: すべての例外を捕捉する
    feclearexcept(FE_ALL_EXCEPT);
   
    // 重要な計算を実行する
    double result = sqrt(-1.0);  // 無効な操作
   
    if (fetestexcept(FE_INVALID)) {
        printf("Invalid operation detected in sensitive calculation\n");
    }
   
    // 元の環境を復元する
    fesetenv(&env);
}

int main() {
    sensitive_calculation();
    return 0;
}

例外の一時的なマスク

例

#include <fenv.h>
#include <stdio.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON

void temp_disable_exceptions() {
    fenv_t env;
   
    // 環境を保存し、すべての例外を一時的にマスクする
    feholdexcept(&env);
   
    // 例外を生成する可能性のある演算を実行する
    double x = 0.0;
    double y = 1.0 / x;  // 例外は発生しない
   
    printf("Division by zero was performed but not reported\n");
   
    // 環境を復元し、以前に発生した例外を処理する
    feupdateenv(&env);
   
    // これで例外をチェックできる
    if (fetestexcept(FE_DIVBYZERO)) {
        printf("Division by zero was detected after restoring environment\n");
    }
}

int main() {
    temp_disable_exceptions();
    return 0;
}

正確な計算の例

例

#include <fenv.h>
#include <stdio.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON

double precise_sum(double a, double b) {
    int original_round = fegetround();
    fenv_t env;
   
    // 環境を保存し、最高精度モードを設定する
    fegetenv(&env);
    fesetround(FE_TONEAREST);
    feclearexcept(FE_ALL_EXCEPT);
   
    double result = a + b;
   
    // 計算が正確かどうかをチェックする
    if (fetestexcept(FE_INEXACT)) {
        printf("Warning: Sum was not exact, possible precision loss\n");
    }
   
    // 元の環境を復元する
    fesetround(original_round);
    fesetenv(&env);
   
    return result;
}

int main() {
    double a = 1.0 / 3.0;
    double b = 2.0 / 3.0;
   
    printf("Sum: %.17g\n", precise_sum(a, b));
    return 0;
}

例外処理ラッパー

例

#include <fenv.h>
#include <stdio.h>
#include <math.h>

#pragma STDC FENV_ACCESS ON

typedef double (*math_func)(double);

double safe_math(math_func f, double x, int *error) {
    fenv_t env;
    *error = 0;
   
    feclearexcept(FE_ALL_EXCEPT);
    fegetenv(&env);
   
    double result = f(x);
   
    int exceptions = fetestexcept(FE_ALL_EXCEPT);
    if (exceptions) {
        *error = exceptions;
        if (exceptions & FE_INVALID) printf("Invalid operation\n");
        if (exceptions & FE_DIVBYZERO) printf("Division by zero\n");
        if (exceptions & FE_OVERFLOW) printf("Overflow\n");
        if (exceptions & FE_UNDERFLOW) printf("Underflow\n");
        if (exceptions & FE_INEXACT) printf("Inexact result\n");
    }
   
    fesetenv(&env);
    return result;
}

int main() {
    int error;
   
    double a = safe_math(sqrt, -1.0, &error);
    printf("sqrt(-1) = %f (error: %d)\n", a, error);
   
    double b = safe_math(log, 0.0, &error);
    printf("log(0) = %f (error: %d)\n", b, error);
   
    return 0;
}

5、注意事項

  • 浮動小数点環境機能を使用する前に、通常は#pragma STDC FENV_ACCESS ONを有効にして、コンパイラに浮動小数点環境操作を最適化で削除しないように指示する必要があります

  • すべての実装がすべての機能をサポートしているわけではありません。使用前にマクロFE_DFL_ENVが定義されているかどうかを確認してください

  • <fenv.h>はC99以降でのみ使用可能です。

  • 浮動小数点例外の検出と丸めモード制御の具体的な動作は、ハードウェアとコンパイラの実装に依存する場合があります。

  • 一部のプラットフォームでは、浮動小数点例外がデフォルトでマスクされている場合があり、feenableexceptなどの関数で明示的に有効にする必要があります。

  • 浮動小数点環境の操作はパフォーマンスに影響を与える可能性があるため、慎重に使用してください。。

<fenv.h>は、浮動小数点例外の検出と処理、丸めモードの設定、浮動小数点状態の問い合わせと変更など、浮動小数点環境の制御機能を提供します。これは、高精度の浮動小数点計算と浮動小数点問題のデバッグに役立つ強力なツールであり、特に科学計算や工学アプリケーションに適しています。<fenv.h>を使用することで、開発者は浮動小数点演算の動作をより適切に制御し、計算結果の正確性と信頼性を確保できます。

その他の拡張