Управление средой вещественной арифметики
Средства управления средой вещественной арифметики, включенные в стандарт POSIX-2001, позволяют выполнить требования двух других стандартов – вещественной арифметики (IEC 60559:1989) и языка C (ISO/IEC 9899:1999).
Среда вещественной арифметики включает сущности двух видов: флаги состояния и управляющие режимы.
Флаг состояния вещественной арифметики – это системная переменная, значение которой устанавливается (но никогда не очищается) при возбуждении исключительной ситуации и содержит дополнительную информацию о случившемся исключении.
Под управляющим режимом вещественной арифметики также понимается системная переменная, но ее значение может быть установлено приложением для воздействия на выполнение последующих операций с вещественными числами.
Тип данных fenv_t, определенный в заголовочном файле <fenv.h>, представляет всю среду, тип fexcept_t – совокупность флагов состояния, включая ассоциированную с флагами информацию.
Применительно к вещественной арифметике стандарт POSIX-2001 предусматривает следующие исключительные ситуации: FE_DIVBYZERO (деление на нуль), FE_INEXACT (потеря точности), FE_INVALID (некорректная операция), FE_OVERFLOW (переполнение), FE_UNDERFLOW (исчезновение порядка). Побитное ИЛИ перечисленных констант обозначается как FE_ALL_EXCEPT.
Стандартом специфицированы четыре режима (направления) округления: FE_DOWNWARD (вниз, то есть к минус бесконечности), FE_TONEAREST (к ближайшему представимому), FE_TOWARDZERO (к нулю), FE_UPWARD (вверх, то есть к плюс бесконечности).
Подразумеваемая среда вещественной арифметики (существующая на момент начала выполнения прикладной программы) обозначается константой FE_DFL_ENV, имеющей тип указателя на константный объект типа fenv_t.
Если приложение проверяет флаги состояния, устанавливает собственные управляющие режимы или выполняется в режимах, отличных от подразумеваемого, то при компиляции необходимо воспользоваться управляющим комментарием (#pragma) FENV_ACCESS:
#pragma STDC FENV_ACCESS ON
Опросить и установить текущую среду вещественной арифметики можно с помощью функций fegetenv() и fesetenv() (см.
Функция fesetexceptflag() выполняет обратную операцию. Как и в случае функции fesetenv(), исключительные ситуации при этом не возбуждаются.
Функции fetestexcept(), feclearexcept() и feraiseexcept() (см. листинг 9.40) служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.
#include <fenv.h>
int fetestexcept (int excepts);
int feclearexcept (int excepts);
int feraiseexcept (int excepts);
Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций. (html, txt)
Функция fetestexcept() проверяет, какие из флагов, заданные аргументом excepts, в данный момент установлены; результатом служит их побитное ИЛИ.
Функция feclearexcept() пытается сбросить, а feraiseexcept() – возбудить заданные исключительные ситуации. Побочным эффектом возбуждения ситуаций переполнения (FE_OVERFLOW) и исчезновения порядка (FE_UNDERFLOW) может стать потеря точности (FE_INEXACT).
Опросить и установить режим округления можно с помощью функций fegetround() и fesetround() (см. листинг 9.41).
#include <fenv.h>
int fegetround (void);
int fesetround (int round);
Листинг 9.41. Описание функций опроса и установки режима округления. (html, txt)
Свидетельством неудачного завершения функции fegetround() служит отрицательный результат.
Продемонстрируем применение некоторых функций управления средой вещественной арифметики (см. листинг 9.42).
Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики. (html, txt)
Возможные результаты выполнения приведенной программы показаны на листинге 9.43.
Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики. (html, txt)
Отметим, что и для исключительных ситуаций, и для режима округления подразумеваемые значения равны нулю, но нули это разные: первый свидетельствует об отсутствии исключений, в то время как второй обозначает режим округления до ближайшего представимого числа.
Обратим внимание также на то, что вместе с исключительными ситуациями переполнения и исчезновения порядка устанавливается также флаг потери точности.
В качестве второго примера рассмотрим программу, реализующую некоторые операции интервальной арифметики (см. листинг 9.44).
Листинг 9.44. Пример использования различных режимов округления. (html, txt)
Программа переустанавливает режимы округления, поскольку при сложении нижних границ интервалов округлять нужно вниз, а при сложении верхних – вверх. Разумеется, в функции ditvl_add() для сохранения и восстановления режима округления можно было воспользоваться функциями fegetround()/fesetround(), а не fegetenv()/fesetenv().
На листинге 9.45 показаны возможные результаты выполнения приведенной программы.
Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики. (html, txt)
Отметим, что в завершающей части программы подразумеваемая среда вещественной арифметики оказалась успешно восстановленной.
листинг 9.36).
#include <fenv.h>
int fegetenv (fenv_t *fenvp);
int fesetenv (const fenv_t *fenvp);
Листинг 9.36. Описание функций опроса и установки текущей среды вещественной арифметики.
Отметим, что функция fesetenv() не возбуждает исключительных ситуаций, она только задает значения флагов состояния. Нормальным для обеих функций является нулевой результат.
Сохранение текущей среды может сочетаться с ее изменением. Так, функция feholdexcept() (см. листинг 9.37) не только запоминает текущую среду по указателю fenvp, но также очищает флаги состояния и устанавливает "безостановочный" режим (продолжать выполнение при возникновении исключительных ситуаций вещественной арифметики). Очевидно, пользоваться функцией feholdexcept() имеет смысл, если, помимо безостановочного, реализация предоставляет другие режимы обработки исключений.
#include <fenv.h> int feholdexcept (fenv_t *fenvp);
Листинг 9.37. Описание функции feholdexcept().
Функция feupdateenv() (см. листинг 9.38) выполняет еще более сложные действия. Она сохраняет в своей локальной памяти информацию о текущей исключительной ситуации, устанавливает новую среду по аргументу fenvp и затем пытается возбудить в ней сохраненное исключение. Подобные манипуляции полезны, когда массовые вычисления производятся в безостановочном режиме, а затем режим меняется и обрабатывается все то нехорошее, что накопилось за это время.
#include <fenv.h> int feupdateenv (const fenv_t *fenvp);
Листинг 9.38. Описание функции feupdateenv().
Для опроса и установки флагов состояния стандартом POSIX-2001 предусмотрены функции fegetexceptflag() и fesetexceptflag() (см. листинг 9.39).
#include <fenv.h>
int fegetexceptflag (fexcept_t *flagp, int excepts);
int fesetexceptflag (const fexcept_t *flagp, int excepts);
Листинг 9.39. Описание функций опроса и установки флагов состояния среды вещественной арифметики.
Функция fegetexceptflag() помещает по указателю flagp зависящее от реализации представление флагов, заданных аргументом excepts, и ассоциированной с ними информации.
Функция fesetexceptflag() выполняет обратную операцию. Как и в случае функции fesetenv(), исключительные ситуации при этом не возбуждаются.
Функции fetestexcept(), feclearexcept() и feraiseexcept() (см. листинг 9.40) служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.
#include <fenv.h>
int fetestexcept (int excepts);
int feclearexcept (int excepts);
int feraiseexcept (int excepts);
Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций.
Функция fetestexcept() проверяет, какие из флагов, заданные аргументом excepts, в данный момент установлены; результатом служит их побитное ИЛИ.
Функция feclearexcept() пытается сбросить, а feraiseexcept() – возбудить заданные исключительные ситуации. Побочным эффектом возбуждения ситуаций переполнения (FE_OVERFLOW) и исчезновения порядка (FE_UNDERFLOW) может стать потеря точности (FE_INEXACT).
Опросить и установить режим округления можно с помощью функций fegetround() и fesetround() (см. листинг 9.41).
#include <fenv.h>
int fegetround (void);
int fesetround (int round);
Листинг 9.41. Описание функций опроса и установки режима округления.
Свидетельством неудачного завершения функции fegetround() служит отрицательный результат.
Продемонстрируем применение некоторых функций управления средой вещественной арифметики (см. листинг 9.42).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение некоторых функций */ /* управления средой вещественной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <fenv.h>
#pragma STDC FENV_ACCESS ON
int main (void) { double d1, d2, d3, s; int res;
printf ("Представление флагов состояния вещественной " "арифметики\n"); printf (" FE_DIVBYZERO: %x\n", FE_DIVBYZERO); printf (" FE_INEXACT: %x\n", FE_INEXACT); printf (" FE_INVALID: %x\n", FE_INVALID); printf (" FE_OVERFLOW: %x\n", FE_OVERFLOW); printf (" FE_UNDERFLOW: %x\n", FE_UNDERFLOW);
printf ("Представление режимов округления\n"); printf (" FE_DOWNWARD: %x\n", FE_DOWNWARD); printf (" FE_TONEAREST: %x\n", FE_TONEAREST); printf (" FE_TOWARDZERO: %x\n", FE_TOWARDZERO); printf (" FE_UPWARD: %x\n", FE_UPWARD);
printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущий режим округления: %x\n", fegetround ());
feclearexcept (FE_ALL_EXCEPT);
/* Вызовем ситуацию исчезновения порядка */ d1 = 1; do { d1 /= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^-inf: %g\n", d1);
feclearexcept (res); /* Вызовем ситуацию переполнения */ d2 = 1; do { d2 *= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^+inf: %g\n", d2);
feclearexcept (res);
/* Вызовем ситуацию деления на нуль */ d3 = 1 / d1; res = fetestexcept (FE_ALL_EXCEPT); printf ("Исключительные ситуации: %x\n", res); printf ("1/0: %g\n", d3);
feclearexcept (res);
/* Пример того, как может возникать потеря точности */ s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s – 2) > 0); printf ("Исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("sqrt (2): %g\n", s);
return 0; }
Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики.
Возможные результаты выполнения приведенной программы показаны на листинге 9.43.
Представление флагов состояния вещественной арифметики FE_DIVBYZERO: 4 FE_INEXACT: 20 FE_INVALID: 1 FE_OVERFLOW: 8 FE_UNDERFLOW: 10 Представление режимов округления FE_DOWNWARD: 400 FE_TONEAREST: 0 FE_TOWARDZERO: c00 FE_UPWARD: 800 Текущие исключительные ситуации: 0 Текущий режим округления: 0 Исключительные ситуации: 30 2^-inf: 0 Исключительные ситуации: 28 2^+inf: inf Исключительные ситуации: 4 1/0: inf Исключительные ситуации: 20 sqrt (2): 1.41421
Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики.
Отметим, что и для исключительных ситуаций, и для режима округления подразумеваемые значения равны нулю, но нули это разные: первый свидетельствует об отсутствии исключений, в то время как второй обозначает режим округления до ближайшего представимого числа.
Обратим внимание также на то, что вместе с исключительными ситуациями переполнения и исчезновения порядка устанавливается также флаг потери точности.
В качестве второго примера рассмотрим программу, реализующую некоторые операции интервальной арифметики (см. листинг 9.44).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые операции */ /* интервальной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <fenv.h>
#pragma STDC FENV_ACCESS ON
/* Интервальное представление числа */ typedef struct ditvl { double lb; double ub; } ditvl_t;
/* * * * * * * * * * * * * * * * * * * * */ /* Сложение интервалов. */ /* Сумма помещается в выходной аргумент. */ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * */ int ditvl_add (const ditvl_t *a1, const ditvl_t *a2, ditvl_t *res) { fenv_t cfenv;
/* Сохраним текущую среду вещественной арифметики */ if (fegetenv (&cfenv) != 0) { perror ("FEGETENV"); return (-1); }
/* Нижние границы нужно складывать с округлением вниз */ if (fesetround (FE_DOWNWARD) != 0) { perror ("FESETROUND"); return (-1); } res->lb = a1->lb + a2->lb;
/* Верхние границы складываются с округлением вверх */ if (fesetround (FE_UPWARD) != 0) { perror ("FESETROUND"); return (-1); } res->ub = a1->ub + a2->ub;
/* Восстановим среду вещественной арифметики */ if (fesetenv (&cfenv) != 0) { perror ("FESETENV"); return (-1); }
return 0; }
/* * * * * * * * */ /* Унарный минус */ /* * * * * * * * */ int ditvl_uminus (const ditvl_t *a, ditvl_t *res) { res->lb = -(a->ub); res->ub = -(a->lb);
return 0; }
/* * * * * * * * */ /* Вызов функций */ /* * * * * * * * */ int main (void) { ditvl_t pi = {3.141592, 3.141593}; ditvl_t e = {2.718281, 2.718282}; ditvl_t res; ditvl_t tmp;
printf ("Представление числа pi: (%f, %f)\n", pi.lb, pi.ub); printf ("Представление числа e: (%f, %f)\n", e.lb, e.ub);
/* Вычислим сумму pi и e */ (void) ditvl_add (&pi, &e, &res); printf ("Сумма pi и e: (%f, %f)\n", res.lb, res.ub);
/* Вычислим разность pi и e */ (void) ditvl_uminus (&e, &tmp); (void) ditvl_add (&pi, &tmp, &res); printf ("Разность pi и e: (%f, %f)\n", res.lb, res.ub);
printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущие режимы округления: %x\n", fegetround ());
return 0; }
Листинг 9.44. Пример использования различных режимов округления.
Программа переустанавливает режимы округления, поскольку при сложении нижних границ интервалов округлять нужно вниз, а при сложении верхних – вверх. Разумеется, в функции ditvl_add() для сохранения и восстановления режима округления можно было воспользоваться функциями fegetround()/fesetround(), а не fegetenv()/fesetenv().
На листинге 9.45 показаны возможные результаты выполнения приведенной программы.
Представление числа pi: (3.141592, 3.141593) Представление числа e: (2.718281, 2.718282) Сумма pi и e: (5.859873, 5.859875) Разность pi и e: (0.423310, 0.423312) Текущие исключительные ситуации: 0 Текущие режимы округления: 0
Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики.
Отметим, что в завершающей части программы подразумеваемая среда вещественной арифметики оказалась успешно восстановленной.