Рассматривать
#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
будет + Инф, b
будет -Inf, и c
будет NaN. Но я также слышу слухи о том, что, строго говоря, поведение деления с плавающей точкой на ноль не определено и, следовательно, приведенный выше код не может считаться переносимым C ++. (Это теоретически уничтожает целостность моего миллиона строк плюс стек кода. Упс.)
Кто прав?
Обратите внимание, я доволен реализация определена, но я говорю о кошачьей еде, чихании демонов неопределенное поведение Вот.
Деление на ноль, как целое число, так и число с плавающей точкой — неопределенное поведение [Expr.mul] р4:
Двоичный / оператор дает частное, а бинарный оператор% — остаток от деления
первого выражения вторым. Если второй операнд / или% равен нулю, поведение не определено. …
Хотя реализация может дополнительно поддерживать Приложение F которая имеет четко определенную семантику для деления с плавающей точкой на ноль.
Мы можем видеть из этого сообщения об ошибке Clang Clang Sanitizer рассматривает деление с плавающей точкой по стандарту IEC 60559 на ноль как неопределенное что хотя макрос __STDC_IEC_559__ определяется, определяется системными заголовками и, по крайней мере, для clang не поддерживает Приложение F и так для clang остается неопределенное поведение:
Приложение F к стандарту C (поддержка IEC 60559 / IEEE 754) определяет
деление с плавающей точкой на ноль, но лязг (3.3 и 3.4 снимок Debian)
считает это неопределенным. Это неверно:Поддержка Приложения F не является обязательной, и мы не поддерживаем ее.
#если STDC_IEC_559
Этот макрос определяется вашими системными заголовками, а не нами; это
ошибка в заголовках вашей системы. (FWIW, GCC не полностью поддерживает Приложение
F, IIRC, так что это даже не ошибка Clang.)
Это сообщение об ошибке и два других сообщения об ошибках UBSan: деление с плавающей точкой на ноль не является неопределенным а также Clang должен поддерживать Приложение F ISO C (IEC 60559 / IEEE 754) указать, что gcc соответствует Приложение F по отношению к плавающей запятой делим на ноль.
Хотя я согласен с тем, что не до библиотеки C определять STDC_IEC_559 безусловно, проблема специфична для лязга. GCC не полностью поддерживает Приложение F, но по крайней мере его намерение состоит в том, чтобы поддерживать его по умолчанию, и разделение четко определено с ним, если режим округления не изменен. В настоящее время не поддержка IEEE 754 (по крайней мере, базовые функции, такие как обработка деления на ноль) считается плохим поведением.
Это дальнейшая поддержка со стороны GCC Семантика математики с плавающей точкой в GCC Wiki что указывает на то, что —FNo пренебрежимо малых сигнализации это значение по умолчанию, которое согласуется с Документация по опциям оптимизации gcc который говорит:
По умолчанию используется -fno-signaling-nans.
Интересно отметить, что UBSan для лязг по умолчанию в том числе поплавкового деления на ноль под -fsanitize = не определено в то время как GCC не:
Обнаружение деления с плавающей точкой на ноль. В отличие от других подобных опций, -fsanitize = float-делить на ноль не включается параметром -fsanitize = undefined, поскольку деление с плавающей точкой на ноль может быть законным способом получения бесконечностей и NaN.
Видеть это жить для лязга а также жить для GCC.
Стандарт C ++ не обязывает стандарт IEEE 754, потому что это зависит в основном от аппаратной архитектуры.
Если аппаратное обеспечение / компилятор правильно реализуют стандарт IEEE 754, подразделение предоставит ожидаемые INF, -INF и NaN, в противном случае … это зависит.
Неопределенное означает, что реализация компилятора решает, и есть много переменных к этому как архитектура аппаратного обеспечения, эффективность генерации кода, лень разработчика компилятора, и т.д ..
Источник:
Стандарт C ++ утверждает, что деление на 0,0 undefined
Стандарт C ++ 5.6.4
… Если второй операнд / или% равен нулю, поведение не определено
Стандарт С ++ 18.3.2.4
…статический constexpr bool is_iec559;
…56. Истинно, если и только если тип соответствует стандарту 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
Поднялсяделение 0,0 на 0,0 дает NaN и
FE_INVALID
Поднялся
Мы говорим здесь о делении с плавающей точкой, так что на самом деле это определяется реализацией double
деление на ноль не определено.
Если std::numeric_limits<double>::is_iec559
является true
, и это «обычно true
«, тогда поведение четко определено и дает ожидаемые результаты.
Довольно безопасным вариантом было бы добавить:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
… рядом с вашим кодом.
Деление на 0 неопределенное поведение.
Из раздела 5.6 Стандарт C ++ (C ++ 11):
Бинарный
/
оператор дает частное, а двоичный%
оператор
дает остаток от деления первого выражения на
второй. Если второй операнд/
или же%
ноль, поведение
не определено. Для интегральных операндов/
оператор дает алгебраический
частное с любой дробной частью отбрасывается; если частноеa/b
является
представимый в типе результата,(a/b)*b + a%b
равноa
,
Не делается различий между целыми и операндами с плавающей запятой для /
оператор. Стандарт только утверждает, что деление на ноль не определено безотносительно к операндам.
В [expr] / 4 мы имеем
Если во время оценки выражения, результат не определен математически или не в диапазоне представимых значений для его типа, поведение не определено. [Примечание: большинство существующих реализаций C ++ игнорируют целочисленные переполнения. Обработка деления на ноль, формирования остатка с использованием делителя нуля и всех исключений с плавающей запятой варьируется в зависимости от машины и обычно настраивается библиотечной функцией. —Конечная записка]
Акцент мой
Так что по стандарту это неопределенное поведение. Далее говорится, что некоторые из этих случаев на самом деле обрабатываются реализацией и являются настраиваемыми. Таким образом, он не скажет, что он определен реализацией, но даст вам знать, что реализации определяют некоторые из этих действий.
Что касается вопроса автора «Кто прав?», То вполне нормально сказать, что и то и другое ответы верны. Тот факт, что стандарт C описывает поведение как «неопределенное», НЕ диктует, что на самом деле делает базовое оборудование; это просто означает, что если вы хотите, чтобы ваша программа имела смысл в соответствии со стандартом Вы не можете предположить, что аппаратное обеспечение фактически выполняет эту операцию. Но если вы работаете на оборудовании, которое реализует стандарт IEEE, вы обнаружите, что операция фактически выполнена с результатами, предусмотренными стандартом IEEE.
Это также зависит от среды с плавающей запятой.
У cppreference есть детали:
http://en.cppreference.com/w/cpp/numeric/fenv
(нет примеров, хотя).
Это должно быть доступно в большинстве сред C ++ 11 и C99 для настольных компьютеров и серверов. Существуют также специфичные для платформы варианты, которые предшествуют стандартизации всего этого.
Я ожидаю, что включение исключений заставляет код работать медленнее, поэтому, вероятно, по этой причине большинство известных мне платформ отключают исключения по умолчанию.