Может ли плавающее значение с почти нулевым значением вызывать ошибку с делением на нуль?

Всем известно, что вы не должны сравнивать поплавки напрямую, а скорее используете допуск:

float a,b;
float epsilon = 1e-6f;
bool equal = (fabs(a-b) < epsilon);

Мне было интересно, если это относится к сравнению значения до нуля, прежде чем использовать его в разделе.

float a, b;
if (a != 0.0f) b = 1/a; // oops?

Нужно ли также сравнивать с epsilon в этом случае?

Ответ 1

Деление с плавающей запятой на ноль не является ошибкой. Он вызывает исключение с плавающей запятой (которое не является оператором, если вы не активно проверяете их) на реализациях, которые поддерживают исключения с плавающей запятой, и имеет четко определенный результат: либо положительную, либо отрицательную бесконечность (если числитель отличен от нуля) или NAN (если числитель равен нулю).

Также возможно получить бесконечность (и исключение переполнения) в качестве результата, когда знаменатель отличен от нуля, но очень близок к нулю (например, субнормальный), но опять же это не ошибка. Это просто работает с плавающей запятой.

Изменить: Обратите внимание, что, как отметил Эрик в комментариях, этот ответ предполагает требования Приложения F, необязательной части стандартного описания поведения с плавающей запятой C и выравнивания его с IEEE стандарт для плавающей запятой. В отсутствие арифметики IEEE C не определяет деление с плавающей запятой на ноль (и фактически результаты всех операций с плавающей запятой определяются реализацией и могут быть определены как полная бессмыслица и по-прежнему соответствуют стандарту C), поэтому, если вы имеете дело с реалистичной реализацией C, которая не соблюдает плавающие точки IEEE, вам придется обратиться к документации по реализации, которую вы используете, чтобы ответить на этот вопрос.

Ответ 2

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

Некоторые реализации C (и некоторые другие вычислительные среды) могут выполняться в режиме скрытого потока, особенно если используются параметры для высокопроизводительной работы. В этом режиме деление на денормальное значение может привести к тому же результату, что и деление на ноль. Режим скрытого нижнего потока не является редкостью, когда используются векторные (SIMD) инструкции.

Денормальные числа - это числа с минимальным показателем в формате с плавающей запятой, которые настолько малы, что неявный бит значащего значения равен 0 вместо 1. Для IEEE 754 с одинарной точностью это отличные от нуля числа с величиной менее 2 -126. Для двойной точности это отличные от нуля числа с величиной меньше 2 -1022.

Правильная обработка денормальных чисел (в соответствии с IEEE 754) требует дополнительных вычислительных затрат в некоторых процессорах. Чтобы избежать этой задержки, когда она не нужна, процессоры могут иметь режим, который преобразует денормальные операнды в ноль. Затем разделение числа на денормальный операнд даст тот же результат, что и деление на ноль, даже если обычный результат будет конечным.

Как отмечено в других ответах, деление на ноль не является ошибкой в ​​реализациях C, которые принимают Приложение F стандарта C. Не все реализации, которые делают. В тех реализациях, которые этого не делают, вы не можете быть уверенным, включены ли ловушки с плавающей запятой, в частности, ловушка для исключения по отдельности, без дополнительных спецификаций о вашей среде.

В зависимости от вашей ситуации вам также может потребоваться защита от другого кода в вашем приложении, изменяющего среду с плавающей запятой.

Ответ 3

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

double x = 1E-300;
cout << x << endl;
double y = 1E300;
cout << y << endl;
double z = y / x;
cout << z << endl;
cout << (z == std::numeric_limits<double>::infinity()) << endl;

Этот производит следующий вывод:

1e-300
1e+300
inf
1

Ответ 4

Только деление на ровно 0.f поднимет деление на нулевое исключение.

Однако деление на действительно небольшое число может генерировать исключение переполнения - результат настолько велик, что он больше не может быть представлен плавающей точкой. Деление вернет бесконечность.

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

Ответ 5

Нужно ли также сравнивать с epsilon в этом случае?

Вы никогда не получите деление на нулевую ошибку, так как 0.0f представляется точно в IEEE float.

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

Если ваш компилятор использует стандарты IEEE 754 для обработки исключений, затем разделите на ноль, а также деление на значение, которое мало чтобы вызвать переполнение, оба результата приведут к значению +/- infiniti. Это может означать, что вы можете включить проверку на очень маленькие номера (что может привести к переполнению вашей платформы). Например, на Windows, float и double оба соответствуют спецификациям, что может привести к тому, что очень маленький делитель создает +/- бесконечность, точно так же, как нулевое значение.

Если ваш компилятор/платформа не соответствует стандартам с плавающей точкой IEEE 754, то я считаю, что результаты являются специфичными для платформы.