Попередження C ++: ділення подвійного на нуль


98

Випадок 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

9
Я припускаю, що він приймає нуль як ціле число у другому випадку і скасовує попередження, навіть якщо обчислення буде зроблено за допомогою подвійного пізніше (що, на мою думку, повинно бути поведінкою, коли d - подвійне).
Кубіт

10
Це справді питання QI. Ні попередження, ні відсутність попередження - це щось, що вимагається самим стандартом C ++. Ви використовуєте GCC?
StoryTeller - Невідповідна Моніка


5
@StoryTeller Що таке QoI? en.wikipedia.org/wiki/QoI ?
користувач202729

5
Що стосується вашого останнього запитання, "чи 0 такий же, як 0,0?" Відповідь - значення однакові, але, як ви з’ясували, це не означає, що вони однакові. Різні типи! Так само, як "A" не тотожне 65.
Містер Лістер,

Відповіді:


108

Ділення з плаваючою комою на нуль добре визначено IEEE і дає нескінченність (позитивну чи негативну відповідно до значення чисельника (або NaNдля ± 0) ).

Для цілих чисел немає способу представити нескінченність, а мова визначає операцію з невизначеною поведінкою, тому компілятор корисно намагається відвести вас від цього шляху.

Однак у цьому випадку, оскільки чисельник є a double, дільник ( 0) також повинен бути підвищений до подвійного, і немає жодних підстав для попередження тут, хоча і не дає попередження, 0.0тому я думаю, що це помилка компілятора.


8
Однак обидва є поділами з плаваючою комою. В d/0, 0перетворюється на тип d.

43
Зауважте, що для C ++ не потрібно використовувати IEEE 754 (хоча я ніколи не бачив компілятора, використовуючи інший стандарт) ..
Yksisarvinen

1
@hvd, хороший момент, це так виглядає, як помилка компілятора
Motti

14
Я погоджуюся, що він повинен або попереджати в обох випадках, або не попереджати в обох випадках (залежно від того, якою обробкою є компілятор плаваючого поділу за нулем)
MM

8
Досить впевнений, що поділ з плаваючою комою на нуль є і UB - це саме те, що GCC реалізує це згідно IEEE 754. Однак їм це не потрібно робити.
Мартін Боннер підтримує Моніку

42

У стандарті C ++ обидва випадки є невизначеною поведінкою . Може статися все, включаючи форматування вашого жорсткого диска. Не варто сподіватися або покладатися на "повернення інф. Добре" чи будь-яку іншу поведінку.

Компілятор, мабуть, вирішує подати попередження в одному випадку, а не в іншому, але це не означає, що один код є нормальним, а інший - ні. Це лише вигадка щодо попередження генератора попереджень.

Від стандарту C ++ 17 [expr.mul] / 4:

Двійковий /оператор дає коефіцієнт, а двійковий %оператор отримує залишок від ділення першого виразу на другий. Якщо другий операнд /або %дорівнює нулю, поведінка не визначена.


21
Неправда, в арифметиці з плаваючою комою чітко визначено поділ на арифметику з плаваючою комою
Мотті

9
@Motti - Якщо можна обмежитися лише стандартом C ++, таких гарантій немає. Відверто кажучи, обсяг цього питання недостатньо уточнений.
StoryTeller - Невідповідна Моніка

9
@StoryTeller Я досить впевнений (хоча я не дивився на самому стандартному документі для цього) , що , якщо std::numeric_limits<T>::is_iec559є true, то поділ на нуль для TНЕ UB (і на більшості платформ це trueдля doubleі float, хоча бути портативними ви будете потрібно чітко перевірити це за допомогою ifабо if constexpr).
Даніель Н

6
@DanielH - "Розумний" насправді досить суб'єктивний. Якби це питання було позначене мовою-юристом , було б цілком інший (набагато менший) набір розумних припущень.
StoryTeller - Невідповідна Моніка

5
@MM Пам'ятайте, що це саме те, що ви сказали, але нічого більше: невизначене не означає, що вимоги не пред'являються, це означає , що стандарт не встановлює жодних вимог . Я заперечую, що в цьому випадку той факт, що імплементація визначається is_iec559як trueозначає, що реалізація документує поведінку, яку стандарт залишає невизначеною. Просто це один випадок, коли документацію щодо реалізації можна читати програмно. Навіть не єдине: те саме стосується is_moduloпідписаних цілих чисел.

12

Моя найкраща здогадка, щоб відповісти на це конкретне питання, - це те, що компілятор надсилає попередження перед виконанням перетворення 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-by-zero
Попередження про поділ часу на компіляцію на нуль. Це за замовчуванням. Щоб призупинити попереджувальні повідомлення, використовуйте -Wno-div-by-zero. Ділення з плаваючою комою на нуль не попереджується, оскільки це може бути законним способом отримання нескінченності та NaNs.


2
Це не так, як працюють компілятори C ++. Компілятор повинен виконати роздільну здатність перевантаження, /щоб знати, що це поділ. Якби ліва сторона була б Fooоб'єктом, а була б operator/(Foo, int), то це може бути навіть не поділ. Компілятор знає поділ лише тоді, коли обрав built-in / (double, double)за допомогою неявного перетворення правої частини. Але це означає, що НЕ робить поділ на int(0), це робить поділ на double(0).
MSalters

@MSalters Будь ласка, дивіться це. Мої знання про C ++ обмежені, але згідно з посиланням operator /(double, int), безумовно, прийнятні. Потім, він говорить, що перетворення виконується перед будь-якою іншою дією, але GCC може швидко натиснути, якщо T2це ціле число, b == 0і подати попередження, якщо так. Не впевнений, чи це повністю відповідає стандарту, але компілятори мають повну свободу у визначенні попередження та коли вони повинні бути запущені.
Yksisarvinen

2
Тут ми говоримо про вбудований оператор. Це смішно. Це насправді не функція, тому ви не можете прийняти його адресу. Тому ви не можете визначити, чи operator/(double,int)дійсно існує. Наприклад, компілятор може вирішити оптимізувати a/bдля константи b, замінивши його на a * (1/b). Звичайно, це означає, що ви більше не дзвоните operator/(double,double)під час виконання, але швидше operator*(double,double). Але зараз оптимізатор, який подорожує 1/0, - константа, на яку йому доведеться харчуватисяoperator*
MSalters

@MSalters Взагалі поділ з плаваючою комою не можна замінити множенням, ймовірно, крім виняткових випадків, таких як 2.
user202729

2
@ user202729: GCC робить це навіть для цілого поділу. Нехай це на мить зануриться. GCC замінює ціле ділення на ціле множення. Так, це можливо, тому що GCC знає, що працює на кільці (числа по модулю 2 ^ N)
MSalters

9

У цій відповіді я не буду вступати в дебалецію UB / не UB.

Я просто хочу зазначити , що 0і 0.0 різні , незважаючи на 0 == 0.0оцінку в дійсності. 0є intбуквальним і 0.0є doubleбуквальним.

Однак у цьому випадку кінцевий результат той самий: d/0це поділ з плаваючою комою, оскільки dподвійний і тому 0неявно перетворюється на подвійний.


5
Я не бачу, наскільки це доречно, враховуючи, що звичайні арифметичні перетворення визначають поділ doubleна intзасіб, в intякий перетворюється double , і він визначений у стандарті, який 0перетворює в 0.0 (conv.fpint / 2)
MM

@MM OP хоче знати, чи 0те саме0.0
bolov

2
Питання говорить "Є 0 != 0.0?". ОП ніколи не питає, чи вони "однакові". Також мені здається, що питання питання стосується того, чи d/0можна поводитись інакшеd/0.0
ММ

2
@MM - ОП попросив . Вони насправді не демонструють гідного етикету SO з тими редакціями констант.
StoryTeller - Невідповідна Моніка

7

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

Будь-який із двох є UB - не має значення. Цитуючи стандарт:

Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу чи виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього) , до припинення перекладу чи виконання (з видачею діагностичного повідомлення).

(Наголос мій)

Розглянемо попередження " запропонувати круглі дужки навколо призначення, використовуваного як значення істини ". Спосіб сказати компілятору, що ви дійсно хочете використовувати результат призначення, - це явне додавання дужок навколо призначення. Отримане твердження має той же ефект, але воно повідомляє компілятору, що ви знаєте, що ви робите. Те саме можна сказати і про foo/0.0: Оскільки ви чітко говорите компілятору "Це поділ з плаваючою комою", використовуючи 0.0замість 0, компілятор довіряє вам і не видаватиме попередження.


1
Обидва повинні пройти звичайні арифметичні перетворення, щоб привести їх до загального типу, що залишить обидва випадки поділу з плаваючою точкою.
Шафік Ягмур

@ShafikYaghmour Ви пропустили точку у відповіді. Зауважте, що я ніколи не згадував, що таке тип foo. Це навмисно. Ваше твердження справедливо лише в тому випадку, коли fooце тип з плаваючою точкою.
Кассіо Ренан

Я цього не робив, компілятор має тип інформації та розуміє перетворення, можливо, текстовий статичний аналізатор може бути схоплений такими речами, але компілятор не повинен.
Шафік Ягмур

Моя думка полягає в тому, що так, компілятор знає звичайні арифметичні перетворення, але він вирішує не видавати попередження, коли програміст явний. Вся справа в тому, що це, мабуть, не помилка, а натомість це навмисна поведінка.
Кассіо Ренан

Тоді документація, на яку я вказував, є невірною, оскільки обидва випадки є поділом з плаваючою комою. Отже, або документація невірна, або діагностика має помилку.
Шафік Ягмур

4

Це схоже на помилку gcc, документація -Wno-div-by-zero чітко говорить :

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

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

Багато бінарних операторів, які очікують операнди арифметичного чи перелічувального типу, викликають перетворення та дають типи результатів аналогічно. Мета - отримати загальний тип, який також є типом результату. Ця закономірність називається звичайними арифметичними перетвореннями, які визначаються наступним чином:

...

- В іншому випадку, якщо будь-який операнд подвійний, інший перетворюється на подвійний.

та [expr.mul] :

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

Що стосується того, чи ділиться плаваюча точка на нуль - це невизначена поведінка, і як різна реалізація цього стосується, здається, моя відповідь тут . TL; DR; Схоже, що gcc відповідає Додатку F wrt до поділу плаваючої точки на нуль, тому невизначений тут не грає ролі. Відповідь була б різною для Кланг.


2

Ділення з плаваючою комою на нуль поводиться інакше, ніж ціле ділення на нуль.

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


2
Це правда, але незрозуміло, наскільки це має відношення до питання, оскільки поділ з плаваючою комою виконується в обох випадках . У коді OP немає цілого поділу.
Конрад Рудольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.