Деление с помощью предотвращения нуля: проверка выражения делителя не приводит к нулю по сравнению с проверкой того, что делитель не равен нулю?

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

float x, y, z;
...
if (y != 1.0)
    z = x / (y - 1.0);

Другими словами, безопаснее ли следующее?

float divisor = y - 1.0;
if (divisor != 0.0)
    z = x / divisor;

comment
Для второго кода, который вы разместили, вы имеете в виду: if (y != 1.0)?   -  person jrd1    schedule 10.10.2012
comment
Оба результата дают одно и то же (я предполагаю, что вы имели в виду делитель! = 0,0f, разные могут быть с чем-то вроде y = 13,0f-12,0f; (y-1,0f) может работать   -  person SinisterMJ    schedule 10.10.2012
comment
Я считаю, что это одно и то же. И да, никогда не проверяйте равенство с числами с плавающей запятой: y != 1.0 запрещено.   -  person ev-br    schedule 10.10.2012
comment
Упс, я имел в виду «если (делитель != 0,0)». Я обновлю. Спасибо!   -  person Max    schedule 10.10.2012
comment
Если вы используете ленивую инициализацию, лучше всего использовать первую, в остальном нет никакой разницы.   -  person andre    schedule 10.10.2012
comment
Почему вы проверяете именно 0.0 (или 1.0)? Если y - 1.0 достаточно мало или x достаточно велико, вы получите inf даже для divisor != 0, так что это не поможет предотвратить это, и, вероятно, будет проще проверить inf после деления, а затем определить, когда деление даст вам бесконечность. . Таким образом, даже если оба пути кода идентичны, это не имеет никакого значения для безопасности.   -  person Grizzly    schedule 10.10.2012
comment
Обратите внимание, что 1.0 — это не float, а double.   -  person John Dibling    schedule 10.10.2012
comment
@Grizzly: Это имеет смысл. Спасибо за объяснение.   -  person Max    schedule 10.10.2012
comment
@Grizzly: Это если вы можете быть уверены, что деление на ноль даст бесконечность (или NaN для 0,0/0,0). Язык не гарантирует такого поведения.   -  person Keith Thompson    schedule 10.10.2012


Ответы (3)


Это предотвратит деление ровно на ноль, однако это не означает, что в результате все равно не получится +/-inf. Знаменатель может быть достаточно мал, чтобы ответ нельзя было представить с помощью double, и в итоге вы получите inf. Например:

#include <iostream>
#include <limits>

int main(int argc, char const *argv[])
{
    double small = std::numeric_limits<double>::epsilon();
    double large = std::numeric_limits<double>::max() / small;
    std::cout << "small: " << small << std::endl;
    std::cout << "large: " << large << std::endl;
    return 0;
}

В этой программе small не равно нулю, но настолько мало, что large превышает диапазон double и равно inf.

person David Brown    schedule 10.10.2012
comment
y - 1 никогда не может быть denorm_min(), наименьшее из них может быть -epsilon()/2, и это не будет бесконечность. - person kennytm; 10.10.2012
comment
@KennyTM Это хороший момент. Однако ответ все равно может быть inf, если числитель достаточно велик. Я обновлю свой ответ. - person David Brown; 10.10.2012

Предполагая IEEE-754 с плавающей запятой, они эквивалентны.

Это основная теорема арифметики FP, состоящая в том, что для конечных x и y x - y == 0 тогда и только тогда, когда x == y, при условии постепенного снижения значимости.

Если субнормальные результаты сбрасываются до нуля (вместо постепенного потери значимости), эта теорема верна только в том случае, если результат x - y является нормальным. Поскольку 1,0 хорошо масштабируется, y - 1.0 никогда не бывает субнормальным, и поэтому y - 1.0 равно нулю тогда и только тогда, когда y равно точно 1,0, независимо от того, как обрабатывается потеря значимости.

C++, конечно, не гарантирует IEEE-754, но теорема верна для большинства «разумных» систем с плавающей запятой.

person Stephen Canon    schedule 10.10.2012

Между двумя фрагментами кода нет никакой разницы () — на самом деле оптимизатор мог бы даже оптимизировать оба фрагмента в один и тот же двоичный код, предполагая, что больше нет использования переменной divisor.

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

person Sergey Kalinichenko    schedule 10.10.2012
comment
Я бы не стал делать ставку на то, что компиляторы будут выполнять какие-либо очевидно очевидные преобразования вычислений с плавающей запятой, по крайней мере, без специальных флагов командной строки. Арифметика с плавающей запятой очень сложна. Например, это не ассоциативно, и GCC это учитывает. - person ; 10.10.2012