Предупреждение С++: деление двойного на ноль

Случай 1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

Он компилируется без каких-либо предупреждений и распечаток inf. OK, C++ может обрабатывать деление на ноль (см. Его в прямом эфире).

Но,

Случай 2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

Компилятор дает следующее предупреждение (см. Его в прямом эфире):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

Почему компилятор дает предупреждение во втором случае?

Является 0 != 0.0?

Редактировать:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

выход:

Same

Ответ 1

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

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

Однако в этом случае, поскольку числитель является double, делитель (0) также должен быть увеличен до double, и нет никаких оснований давать предупреждение здесь, не выдавая предупреждения для 0.0 поэтому я считаю, что это ошибка компилятора.

Ответ 2

В стандарте C++ оба случая являются неопределенным поведением. Все может произойти, включая форматирование жесткого диска. Вы не должны ожидать или полагаться на "return inf. Ok" или любое другое поведение.

Компилятор, по-видимому, решает дать предупреждение в одном случае, а не в другом, но это не означает, что один код в порядке, а другой нет. Это просто причуда генерации предупреждений компилятора.

Из стандарта C++ 17 [expr.mul]/4:

Двоичный / оператор дает частное, а бинарный оператор % дает остаток от деления первого выражения на второе. Если второй операнд / или % равен нулю, поведение не определено.

Ответ 3

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

Итак, шаги будут такими:

  1. Выражение выражения
  2. Арифметический оператор /(T, T2), где T=double, T2=int.
  3. Убедитесь, что std::is_integral<T2>::value true а b == 0 - это предупреждение.
  4. Предупреждение об эмиссии
  5. Выполнять неявное преобразование T2 в double
  6. Выполните хорошо определенное разделение (поскольку компилятор решил использовать IEEE 754).

Это, конечно, спекуляция и основано на спецификациях, определяемых компилятором. Со стандартной точки зрения мы имеем дело с возможными неопределенными поведением.


Обратите внимание, что это ожидаемое поведение в соответствии с документацией GCC
(кстати, кажется, что этот флаг нельзя использовать явно в GCC 8.1)

-Wdiv на ноль
Предупреждать о целочисленном делении времени компиляции на ноль. Это значение по умолчанию. Чтобы заблокировать предупреждающие сообщения, используйте -Wno-div-by-zero. Деление на плавающие точки на ноль не предупреждается, так как это может быть законным способом получения бесконечностей и NaNs.

Ответ 4

В этом ответе я не буду вдаваться в неудачу UB/не UB.

Я просто хочу указать, что 0 и 0.0 различны, несмотря на то, что 0 == 0.0 оценивается как true. 0 - это int буква, а 0.0 - double литерал.

Однако в этом случае конечный результат одинаков: d/0 является делением с плавающей запятой, поскольку d является double и поэтому 0 неявно преобразуется в double.

Ответ 5

Я бы сказал, что foo/0 и foo/0.0 не совпадают. А именно, результирующий эффект первого (целочисленное деление или деление с плавающей запятой) сильно зависит от типа foo, тогда как то же самое не верно для второго (он всегда будет делением с плавающей запятой).

Независимо от того, является ли какой-либо из них UB, это не имеет значения. Цитирование стандарта:

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

(Акцент мой)

Рассмотрите предложение " предложить круглые скобки вокруг назначения, используемого как значение истины ". Способ сообщить компилятору, что вы действительно хотите использовать результат назначения, является явным и добавляет скобки вокруг назначения. Получающийся оператор имеет тот же эффект, но он сообщает компилятору, что вы знаете, что делаете. То же самое можно сказать и о foo/0.0: поскольку вы явно говорите компилятору "Это деление с плавающей запятой", используя 0.0 вместо 0, компилятор доверяет вам и не выдаст предупреждение.

Ответ 6

Это выглядит как ошибка gcc, документация для -Wno-div-by-zero четко говорит:

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

и после обычных арифметических преобразований, описанных в [expr.arith.conv], оба операнда будут двойными:

Многие двоичные операторы, ожидающие операндов арифметического или перечисляемого типа, вызывают конверсии и дают аналогичные результаты. Цель состоит в том, чтобы дать общий тип, который также является типом результата. Этот шаблон называется обычным арифметическим преобразованием, которое определяется следующим образом:

...

- В противном случае, если один из операндов двойной, другой должен быть преобразован в double.

и [expr.mul]:

Операнды * и/должны иметь арифметический или неперечисленный тип перечисления; операнды% должны иметь интегральный или неперечисленный тип перечисления. Обычные арифметические преобразования выполняются над операндами и определяют тип результата.

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

Ответ 7

Деление с плавающей запятой на ноль ведет себя не так, как целое деление на ноль.

Стандарт с плавающей точкой IEEE различает +inf и -inf, в то время как целые числа не могут хранить бесконечность. Целочисленное деление на нулевой результат - неопределенное поведение. Деление с плавающей запятой на ноль определяется стандартом с плавающей запятой и приводит к +inf или -inf.