Работа с исключениями с плавающей запятой

Я не уверен, как справляться с исключениями с плавающей запятой в C или С++. Из wiki существуют следующие типы исключений с плавающей запятой:

IEEE 754 specifies five arithmetic errors that are to be recorded in "sticky bits" (by default; note that trapping and other alternatives are optional and, if provided, non-default).  

* inexact, set if the rounded (and returned) value is different from the mathematically exact result of the operation.  
* underflow, set if the rounded value is tiny (as specified in IEEE 754) and inexact (or maybe limited to if it has denormalisation loss, as per the 1984 version of IEEE 754), returning a subnormal value (including the zeroes).  
* overflow, set if the absolute value of the rounded value is too large to be represented (an infinity or maximal finite value is returned, depending on which rounding is used).  
* divide-by-zero, set if the result is infinite given finite operands (returning an infinity, either +∞ or −∞).  
* invalid, set if a real-valued result cannot be returned (like for sqrt(−1), or 0/0), returning a quiet NaN.

Это значит, что когда произойдет какой-либо из вышеперечисленных исключений, программа выйдет из строя ненормально? Или программа будет нести эту ошибку без упоминания чего-либо и, следовательно, сделать ошибку сложной для отладки?

Является ли компилятор вроде gcc способным дать предупреждение для некоторого очевидного случая?

Что я могу сделать во время кодирования моей программы, чтобы уведомить, где происходит ошибка, и какие типы это происходит, чтобы я мог легко найти ошибку в моем коде? Пожалуйста, дайте решения как в случае C, так и в С++.

Спасибо и приветствую!

Ответ 1

В Linux вы можете использовать расширение GNU feenableexcept (скрыто в нижней части этой страницы) для включения ловушки в исключениях с плавающей запятой - если вы это сделаете, вы получите сигнал SIGFPE, когда произойдет исключение, которое вы можете затем поймать в своем отладчике. Остерегайтесь, хотя иногда сигнал получает бросок на инструкцию с плавающей запятой после той, которая на самом деле вызывает проблему, давая вводящую в заблуждение информацию о линии в отладчике!

Ответ 2

Существует много вариантов, но общая, а также философия по умолчанию, введенная 754, заключается в не ловушке, а вместо этого создает особые результаты, такие как бесконечности, которые могут или не могут появиться в важных результатах.

В результате функции, которые проверяют состояние отдельных операций, не используются так часто, как функции, которые проверяют представления результатов.

См., например...

LIST OF FUNCTIONS

 Each of the functions that use floating-point values are provided in sin-
 gle, double, and extended precision; the double precision prototypes are
 listed here.  The man pages for the individual functions provide more
 details on their use, special cases, and prototypes for their single and
 extended precision versions.

 int fpclassify(double)
 int isfinite(double)
 int isinf(double)
 int isnan(double)
 int isnormal(double)
 int signbit(double)

Обновление: Для тех, кто действительно думает, что операторы FPU генерируют SIGFPE в случае по умолчанию в наши дни, я бы посоветовал вам попробовать эту программу. Вы можете легко создавать потоки, переполнения и деления на ноль. То, что вы не сгенерируете (если вы не запустите его на последнем выживающем VAX или RISC не-754), это SIGFPE:

#include <stdio.h>
#include <stdlib.h>
int main(int ac, char **av) { return printf("%f\n", atof(av[1]) / atof(av[2])); }

Ответ 3

В Windows с Visual С++ вы можете контролировать, какие исключения с плавающей запятой разоблачены с помощью _control87() и т.д.. Незамасленные исключения с плавающей запятой генерируют структурированные исключения, которые можно обрабатывать с помощью __try/__except (и нескольких других механизмов). Все это полностью зависит от платформы.

Если вы оставите исключения с плавающей запятой в масках, другой подход, зависящий от платформы для обнаружения этих условий, - это очистить статус с плавающей запятой, используя _clear87() и т.д., выполнить вычисления, а затем запросить статус с плавающей запятой, используя _status87() и т.д..

Разве это лучше, чем предложение DigitalRoss по проверке результата? В большинстве случаев это не так. Если вам нужно обнаружить (или контролировать) округление (что маловероятно), то может быть?

В Windows с Borland/CodeGear/Embarcadero С++ некоторые исключения с плавающей запятой по умолчанию деактивируются, что часто вызывает проблемы при использовании сторонних библиотек, которые не были протестированы с исключениями с плавающей запятой, которые были немасштабированы.

Ответ 4

Различные компиляторы обрабатывают эти ошибки по-разному.

Неточность почти всегда является результатом деления чисел с абсолютным значением, большим одного (возможно, через trancendental). Добавление, вычитание и умножение чисел с абсолютным значением > 1.0 может привести только к переполнению.

Недостаточный поток не происходит очень часто и, вероятно, не будет проблемой в обычных вычислениях, кроме повторяющихся функций, таких как серия Тейлора.

Переполнение является проблемой, которая обычно может быть обнаружена путем какого-либо сравнения "бесконечности", разные компиляторы меняются.

Деление на ноль весьма примечательно, так как ваша программа будет (должна) сбой, если у вас нет обработчика ошибок. Проверка дивидендов и делителей поможет избежать проблемы.

Неверные ответы обычно попадают без специальных обработчиков ошибок с напечатанной ошибкой DOMAIN.

[EDIT]

Это может помочь: (Руководство по численным вычислениям Sun) http://docs.sun.com/source/806-3568/

Ответ 5

C99 представили функции для обработки исключений с плавающей запятой. Перед операцией с плавающей запятой вы можете использовать feclearexcept(), чтобы удалить все исключительные исключения. После операции (ов) вы можете использовать fetestexcept() для проверки того, какие флаги исключений установлены.

Ответ 6

В Linux вы можете захватить эти исключения, захватив сигнал SIGFPE. Если вы ничего не сделаете, эти исключения завершат вашу программу. Чтобы установить обработчик, используйте функцию сигнала, передав сигнал, который вы хотите захватить, и функцию, которую нужно вызвать в случае срабатывания сигнала.