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



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



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


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



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


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

580   5  

5 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.