Стандартная библиотека C<fenv.h>

Стандартная библиотека C<fenv.h>

<fenv.h>является заголовочным файлом в стандартной библиотеке C, используемым для управления средой вычислений с плавающей запятой.

<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("Возникло исключение деления на ноль.\n");
    }

    // Установить режим округления к нулю
    fesetround(FE_TOWARDZERO);

    // Проверить режим округления
    double a = 1.5;
    double b = rint(a); // Округление до целого
    printf("Округление 1.5 к нулю: %.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("Произошло деление на ноль!"\n");
    }
   
    // Проверить наличие любого исключения
    if (fetestexcept(FE_ALL_EXCEPT)) {
        printf("Произошло по крайней мере одно исключение с плавающей запятой\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[] = {"К ближайшему", "Вверх", "Вниз", "К нулю"};
   
    for (int i = 0; i < 4; i++) {
        if (fesetround(modes[i]) == 0) {
            printf("Округление %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("Обнаружена недопустимая операция в чувствительном вычислении\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("Деление на ноль было выполнено, но не зарегистрировано\n");
   
    // Восстановить окружение и обработать исключения, возникшие ранее
    feupdateenv(&env);
   
    // Теперь исключения можно проверить
    if (fetestexcept(FE_DIVBYZERO)) {
        printf("Деление на ноль было обнаружено после восстановления окружения\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("Предупреждение: сумма не была точной, возможна потеря точности\n");
    }
   
    // Восстановить исходную среду
    fesetround(original_round);
    fesetenv(&env);
   
    return result;
}

int main() {
    double a = 1.0 / 3.0;
    double b = 2.0 / 3.0;
   
    printf("Сумма: %.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("Недопустимая операция\n");
        if (exceptions & FE_DIVBYZERO) printf("Деление на ноль\n");
        if (exceptions & FE_OVERFLOW) printf("Переполнение\n");
        if (exceptions & FE_UNDERFLOW) printf("Исчезновение порядка\n");
        if (exceptions & FE_INEXACT) printf("Неточный результат\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>, разработчики могут лучше контролировать поведение операций с плавающей запятой, обеспечивая точность и надёжность результатов вычислений.

Другие расширения