Случай 1:
#include <iostream>
int main()
{
double d = 15.50;
std::cout<<(d/0.0)<<std::endl;
}
Компилируется без предупреждений и выводов inf
. Хорошо, 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
c++
gcc
floating-point
divide-by-zero
Джайеш
источник
источник
Ответы:
Деление с плавающей запятой на ноль хорошо определено IEEE и дает бесконечность (положительную или отрицательную в зависимости от значения числителя (или
NaN
± 0) ).Для целых чисел нет способа представить бесконечность, и язык определяет операцию с неопределенным поведением, поэтому компилятор услужливо пытается увести вас с этого пути.
Однако в этом случае, поскольку числитель - это
double
, divisor (0
) также должен быть повышен до двойного, и нет причин давать здесь предупреждение, не давая предупреждения,0.0
поэтому я думаю, что это ошибка компилятора.источник
d/0
,0
преобразуется к типуd
.В стандартном C ++ оба случая являются неопределенным поведением . Может случиться что угодно, включая форматирование жесткого диска. Вы не должны ожидать или полагаться на «return inf. Ok» или любое другое поведение.
Компилятор явно решает выдать предупреждение в одном случае, а не в другом, но это не означает, что один код в порядке, а другой - нет. Это просто причуда генерации предупреждений компилятором.
Из стандарта C ++ 17 [expr.mul] / 4:
источник
std::numeric_limits<T>::is_iec559
естьtrue
, то деление на ноль дляT
не UB (и на большинстве платформ оноtrue
дляdouble
иfloat
, хотя для переносимости вы необходимо явно проверить это с помощьюif
илиif constexpr
).is_iec559
какtrue
означает, что реализация документирует поведение, которое стандарт оставляет неопределенным. Просто это тот случай, когда документацию по реализации можно читать программно. Даже не единственный: то же самое относится и кis_modulo
целочисленным типам со знаком.Лучше всего, чтобы ответить на этот конкретный вопрос , компилятор выдает предупреждение перед преобразованием
int
вdouble
.Итак, шаги будут такими:
/(T, T2)
, гдеT=double
,T2=int
.std::is_integral<T2>::value
естьtrue
иb == 0
- это вызывает предупреждение.T2
вdouble
Это, конечно, предположение и основано на спецификациях, определенных компилятором. Со стандартной точки зрения, мы имеем дело с возможным неопределенным поведением.
Обратите внимание, что это ожидаемое поведение в соответствии с документацией GCC
(кстати, похоже, что этот флаг нельзя использовать явно в GCC 8.1)
источник
/
чтобы знать, что это деление. Если бы левая сторона былаFoo
объектом, а там был быoperator/(Foo, int)
, то это могло бы даже не быть деление. Компилятор знает это деление только тогда, когда он выбралbuilt-in / (double, double)
неявное преобразование правой части. Но это означает, что он НЕ выполняет деление наint(0)
, он выполняет деление наdouble(0)
.operator /(double, int)
, безусловно, приемлемы. Затем он говорит, что преобразование выполняется до любого другого действия, но GCC может сжать быструю проверку,T2
является ли тип целочисленным,b == 0
и выдать предупреждение, если это так. Не уверен, что это полностью соответствует стандарту, но компиляторы имеют полную свободу в определении предупреждений и времени их запуска.operator/(double,int)
существует ли на самом деле. Например, компилятор может решить оптимизироватьa/b
для константыb
, заменив ее наa * (1/b)
. Конечно, это означает, что вы больше не звонитеoperator/(double,double)
во время выполнения, а быстрееoperator*(double,double)
. Но теперь отключается оптимизатор1/0
, константа, которой он должен был бы питатьсяoperator*
В этом ответе я не буду вдаваться в разгром UB / not UB.
Я просто хочу указать на это,
0
и0.0
они разные, несмотря на то, что их0 == 0.0
оценка истинна.0
являетсяint
буквальным и0.0
являетсяdouble
буквальным.Однако в этом случае конечный результат тот же:
d/0
деление с плавающей запятой, потому чтоd
оно double и поэтому0
неявно преобразуется в double.источник
double
наint
средство, вint
которое конвертируетсяdouble
, и это указано в стандарте, который0
преобразуется в0.0
(conv.fpint / 2)0
то же ли это, что и0.0
0 != 0.0
?». OP никогда не спрашивает, «одинаковы ли они». Также мне кажется, что цель вопроса состоит в том,d/0
можно ли вести себя иначе, чемd/0.0
Я считаю , что
foo/0
иfoo/0.0
это не то же самое. А именно, результирующий эффект первого (целочисленное деление или деление с плавающей запятой) сильно зависит от типаfoo
, в то время как то же самое не верно для второго (это всегда будет деление с плавающей запятой).Не имеет значения, является ли какой-либо из двух UB. Цитата из стандарта:
(Акцент мой)
Обратите внимание на предупреждение « предлагать круглые скобки вокруг присваивания, используемого в качестве истинного значения »: способ сообщить компилятору, что вы действительно хотите использовать результат присваивания, явиться явным и добавить круглые скобки вокруг присваивания. Результирующий оператор имеет тот же эффект, но сообщает компилятору, что вы знаете, что делаете. То же самое можно сказать и о
foo/0.0
: поскольку вы явно указываете компилятору «Это деление с плавающей запятой», используя0.0
вместо0
, компилятор доверяет вам и не выдаст предупреждение.источник
foo
. Это сделано намеренно. Ваше утверждение верно только в том случае, еслиfoo
это тип с плавающей запятой.Это похоже на ошибку gcc, в документации
-Wno-div-by-zero
четко сказано :и после обычных арифметических преобразований, описанных в [expr.arith.conv], оба операнда будут двойными :
и [expr.mul] :
Что касается того, является ли деление с плавающей запятой на ноль неопределенным поведением, и как разные реализации с этим справляются, мой ответ здесь . TL; DR; Похоже, что gcc соответствует Приложению F относительно деления с плавающей запятой на ноль, поэтому undefined здесь не играет роли. Для clang ответ был бы другим.
источник
Деление с плавающей запятой на ноль ведет себя иначе, чем целочисленное деление на ноль.
Стандарт IEEE с плавающей запятой различает + inf и -inf, в то время как целые числа не могут хранить бесконечность. Целочисленное деление на нулевой результат - неопределенное поведение. Деление с плавающей запятой на ноль определяется стандартом с плавающей запятой и приводит к + inf или -inf.
источник