Поведение деления с плавающей запятой на ноль

считают

#include <iostream>
int main()
{
    double a = 1.0 / 0;
    double b = -1.0 / 0;
    double c = 0.0 / 0;
    std::cout << a << b << c; // to stop compilers from optimising out the code.    
}

Я всегда думал, что a будет +Inf,b будет-Inf, и c будет NaN. Но я также слышу слухи, что строго говоря поведение деления с плавающей запятой на ноль undefined и поэтому приведенный выше код не может считаться переносимым C++. (Это теоретически уничтожает целостность моего миллиона строк плюс стек кода. Ой.)

кто правильный?

примечание Я доволен реализации, но я говорю о кошачьей еде, демоническом чихании неопределено поведение здесь.

7 ответов


деление на ноль как целое число, так и с плавающей запятой являются неопределенным поведением [expr.mul]p4:

оператор binary / дает фактор, а оператор binary % дает остаток от деления от первого выражения ко второму. если второй операнд / или % равен нулю, поведение не определено. ...

хотя реализация может дополнительно поддерживать Приложение F который хорошо определен семантика деления с плавающей запятой на ноль.

мы можем видеть из этого отчета об ошибке clang дезинфицирующее средство clang рассматривает деление IEC 60559 с плавающей запятой на ноль как неопределенное это даже при том, что макрос _ _ STDC_IEC_559__ определяется, определяется системными заголовками и, по крайней мере, для clang не поддерживает Приложение F и поэтому для clang остается неопределенным поведение:

приложение F к стандарту C (МЭК 60559 / поддержка IEEE 754) определяет деление с плавающей запятой на ноль, но clang (3.3 и 3.4 снимок Debian) считает его неопределенным. Это неверно:

поддержка приложения F является факультативной, и мы ее не поддерживаем.

#if STDC_IEC_559

этот макрос определяется вашими системными заголовками, а не нами; это ошибка в заголовках вашей системы. (FWIW, GCC не полностью поддерживает приложение F либо, IIRC, так что это даже не ошибка, специфичная для Clang.)

этот отчет об ошибке и два других отчета об ошибке утилиты: делением с плавающей запятой на ноль не определено и clang должен поддерживать приложение F ISO C (IEC 60559 / IEEE 754) укажите, что gcc соответствует Приложение F относительно деления с плавающей запятой на ноль.

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

это дальнейшая поддержка со стороны gcc семантика математики с плавающей запятой в GCC wiki что означает, что - fno-сигнализация-nans является значением по умолчанию, которое согласуется с документация по параметрам оптимизации gcc Он говорит:

значение по умолчанию-fno-signaling-nans.

интересно отметить, что утилиты для clang по умолчанию включает float-divide-by-zero под - fsanitize=undefined пока gcc не:

обнаружение деления с плавающей запятой на ноль. В отличие от других подобных вариантов, - fsanitize=float-divide-by-zero не включен-fsanitize=undefined, так как деление с плавающей запятой на ноль может быть законным способом получения бесконечностей и NaNs.

видеть жить для clang и жить для gcc.


стандарт C++ не заставляет стандарт IEEE 754, потому что это зависит в основном от аппаратной архитектуры.

Если аппаратное обеспечение / компилятор правильно реализуют стандарт IEEE 754, подразделение предоставит ожидаемые INF, - INF и NaN, в противном случае... это зависит от.

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

источник:

в стандарт C++, что деление на 0.0 составляет undefined

Стандарт C++ 5.6.4

... Если второй операнд / или % равен нулю, поведение не определено

Стандарт C++ 18.3.2.4

...статический constexpr bool is_iec559;

...56. True только в том случае, если тип соответствует стандарту IEC 559.217

...57. Значимый для всех типы с плавающей запятой.

C++ обнаружение IEEE754:

стандартная библиотека включает шаблон для определения, поддерживается ли IEEE754:

статический constexpr bool is_iec559;

#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();

что если IEEE754 не поддерживается?

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


цитирую cppreference:

если второй операнд равен нулю, поведение не определено, за исключением того, что если происходит деление с плавающей запятой и тип поддерживает арифметику с плавающей запятой IEEE (см. std::numeric_limits::is_iec559), то:

  • если один операнд NaN, результат NaN

  • деления ненулевого числа на ±0.0 дает правильно-бесконечность и FE_DIVBYZERO is подняли

  • деление 0.0 на 0.0 дает NaN и FE_INVALID подняли

мы говорим о делении с плавающей запятой здесь, поэтому на самом деле реализация-определяется ли double деление на ноль неопределено.

если std::numeric_limits<double>::is_iec559 is true, а это "обычно true", тогда поведение четко определено и дает ожидаемые результаты.

довольно безопасная ставка была бы плюх вниз:

static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");

... рядом с вашим кодом.


деление на 0 -неопределенное поведение.

из раздела 5.6 стандарт C++ (C++11):

бинарные / оператор дает фактор, а двоичный % оператор возвращает остаток от деления первого выражения второй. если второй операнд / или % равно нулю поведение не определено. для целочисленных операндов / оператор дает алгебраический фактор с любой дробной частью отбрасывается; если фактор a/b is представимо в виде результата,(a/b)*b + a%b равна a .

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


в [expr] / 4 у нас есть

Если во время вычисления выражения, результат не определен математически или не в диапазоне представимых значений для ее типа поведение не определено. [ Примечание: большинство существующих реализаций C++ игнорируют целочисленные переполнения. Обработка деления на ноль, формирование остатка с использованием делителя нуля, и все исключения с плавающей запятой различаются между машинами и обычно регулируются библиотекой функция. - конец Примечания ]

выделено мной

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


Что касается вопроса отправителя: "кто прав?', это совершенно нормально говорить, что и ответы верны. Тот факт, что стандарт C описывает поведение как "неопределенное", не диктует, что на самом деле делает базовое оборудование; это просто означает, что Если вы хотите, чтобы ваша программа была значимой в соответствии со стандартом вы -не можете предположить, - что оборудование фактически реализует эту операцию. Но если вы работаете на оборудовании, которое реализуя стандарт IEEE, вы обнаружите, что операция фактически реализована, с результатами, предусмотренными стандартом IEEE.


Это также зависит от среды с плавающей запятой.

cppreference и детали: http://en.cppreference.com/w/cpp/numeric/fenv (хотя примеров нет).

Это должно быть доступно в большинстве сред рабочего стола/сервера C++11 и C99. Существуют также специфические для платформы вариации, которые предшествовали стандартизации всего этого.

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