Стандартная библиотека 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 <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 <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 <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 <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 <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 <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 <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>, разработчики могут лучше контролировать поведение операций с плавающей запятой, обеспечивая точность и надёжность результатов вычислений.