Поведение деления с плавающей запятой на ноль
Рассмотрим
#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, и поэтому приведенный выше код не может считаться портативным С++. (Это теоретически уничтожает целостность моего миллиона строк плюс код.).
Кто исправит?
Примечание. Я доволен реализацией, но я говорю о поведении кошачьих, демонах чихания undefined здесь.
Ответы
Ответ 1
Деление на ноль, как целое число, так и число с плавающей точкой - неопределенное поведение [expr.mul] p4:
Двоичный/оператор дает частное, а двоичный оператор% - остаток от деления первого выражения на второе. Если второй операнд/или% равен нулю, поведение не определено....
Хотя реализация может дополнительно поддерживать Приложение F, которое имеет четко определенную семантику для деления с плавающей запятой на ноль.
Из этого отчета об ошибке в clang мы можем заметить, что clang sanitizer считает деление с плавающей запятой IEC 60559 на ноль неопределенным, хотя макрос __STDC_IEC_559__ определен, он определяется системными заголовками и, по крайней мере, для clang, не поддерживает Приложение F, и поэтому для лязга остается неопределенное поведение:
Приложение F к стандарту C (поддержка IEC 60559/IEEE 754) определяет деление с плавающей запятой на ноль, но clang (снимок Debian 3.3 и 3.4) считает его неопределенным. Это неверно:
Поддержка Приложения F не является обязательной, и мы не поддерживаем ее.
#if STDC_IEC_559
Этот макрос определяется вашими системными заголовками, а не нами; это ошибка в заголовках вашей системы. (FWIW, GCC также не полностью поддерживает Приложение F, IIRC, так что это даже не ошибка, специфичная для Clang.)
Этот отчет об ошибке и два других отчета об ошибках UBSan: деление с плавающей точкой на ноль не является неопределенным, и clang должен поддерживать Приложение F ISO C (IEC 60559/IEEE 754), что указывает, что gcc соответствует Приложению F в отношении деления с плавающей запятой на ноль,
Хотя я согласен, что определение библиотеки STDC_IEC_559 не зависит от библиотеки C, проблема специфична для clang. GCC не полностью поддерживает Приложение F, но по крайней мере его намерение состоит в том, чтобы поддерживать его по умолчанию, и разделение четко определено с ним, если режим округления не изменен. В настоящее время не поддержка IEEE 754 (по крайней мере, базовые функции, такие как обработка деления на ноль) считается плохим поведением.
Это также поддерживается семантикой gcc семантики с плавающей точкой в вики GCC, которая указывает, что -fno-signaling-nans является значением по умолчанию, что согласуется с документацией по опциям оптимизации gcc, которая гласит:
По умолчанию используется -fno-signaling-nans.
Интересно отметить, что UBSan для clang по умолчанию включает значение float-делить на ноль в -fsanitize = undefined, в то время как gcc этого не делает:
Обнаружение деления с плавающей точкой на ноль. В отличие от других аналогичных опций, -fsanitize = float-делить на ноль не разрешается с помощью -fsanitize = undefined, поскольку деление с плавающей точкой на ноль может быть законным способом получения бесконечностей и NaN.
Смотрите это жить для лязг и жить для GCC.
Ответ 2
Стандарт C++ не заставляет стандарт IEEE 754, поскольку это в основном зависит от архитектуры оборудования.
Если аппаратное обеспечение/компилятор правильно реализует стандарт IEEE 754, подразделение будет предоставлять ожидаемые INF, -INF и NaN, в противном случае... это зависит.
Undefined означает, что реализация компилятора решает, и для этого есть много переменных, таких как аппаратная архитектура, эффективность генерации кода, ленивость разработчика компилятора и т.д.
Источник:
Стандарт C++ определяет, что деление на 0,0 не undefined
C++ Стандарт 5.6.4
... Если второй операнд/или% равен нулю, поведение не определено
C++ Стандарт 18.3.2.4
... static constexpr bool is_iec559;
... 56. Истина тогда и только тогда, когда тип придерживается стандарта IEC 559.217
... 57. Значение для всех типов с плавающей запятой.
C++ обнаружение IEEE754:
Стандартная библиотека включает в себя шаблон для определения поддержки IEEE754 или нет:
static constexpr bool is_iec559;
#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();
Что делать, если IEEE754 не поддерживается?
Это зависит, как правило, деление на 0 запускает аппаратное исключение и завершает работу приложения.
Ответ 3
Цитирование cppreference:
Если второй операнд равен нулю, поведение undefined, за исключением того, что если происходит деление с плавающей запятой и тип поддерживает арифметику с плавающей запятой IEEE (см. std::numeric_limits::is_iec559
), затем:
-
если один операнд NaN, результатом является NaN
-
деление ненулевого числа на ± 0,0 дает корректно подписанную бесконечность и FE_DIVBYZERO
поднято
-
Деление 0.0 на 0.0 дает NaN и FE_INVALID
поднято
Здесь мы говорим о делении с плавающей запятой, поэтому на самом деле это определяется реализацией, если double
деление на ноль равно undefined.
Если std::numeric_limits<double>::is_iec559
- true
, и это "обычно true
" , то поведение хорошо определено и производит ожидаемые результаты.
Довольно безопасная ставка заключалась бы в том, чтобы сбрасывать:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
... рядом с вашим кодом.
Ответ 4
Разделение на 0 - undefined поведение.
Из раздела 5.6 Стандарт С++ (С++ 11):
Двоичный оператор /
дает частное, а двоичный оператор %
дает остаток от деления первого выражения на второй. Если второй операнд /
или %
равен нулю, поведение undefined.. Для интегральных операндов оператор /
дает алгебраическую частное с какой-либо дробной частью, отброшенной; если фактор a/b
равен представимый в типе результата, (a/b)*b + a%b
равен a
.
Никакого различия между целыми и операндами с плавающей запятой для оператора /
не существует. Стандарт только утверждает, что деление на ноль undefined независимо от операндов.
Ответ 5
В [expr]/4 имеем
Если во время оценки выражения результат не определяется математически или нет в диапазоне отображаемых значений для его типа, поведение undefined. [Примечание: большинство существующих реализаций С++ игнорируют целые переполнения. Обработка деления на ноль, формирование остатка с использованием делителя нуля, и все исключения с плавающей запятой различаются между машинами и обычно регулируются библиотечной функцией. -end note]
Акцент на мине
В соответствии со стандартом это поведение undefined. Далее говорится, что некоторые из этих случаев фактически обрабатываются реализацией и настраиваются. Поэтому он не скажет, что это реализация, но она позволяет вам знать, что реализации действительно определяют некоторые из этих действий.
Ответ 6
Что касается вопроса о кандидате "Кто правильно?", то вполне нормально сказать, что оба ответа верны. Тот факт, что стандарт C описывает поведение как "undefined", не диктует, что на самом деле делает базовое оборудование; это просто означает, что если вы хотите, чтобы ваша программа имела смысл в соответствии со стандартом, вы, возможно, не предполагаете, что аппаратное обеспечение фактически реализует эту операцию. Но если вы работаете на аппаратном обеспечении, реализующем стандарт IEEE, вы обнаружите, что операция фактически реализована с результатами, предусмотренными стандартом IEEE.
Ответ 7
Это также зависит от среды с плавающей запятой.
cppreference содержит подробную информацию:
http://en.cppreference.com/w/cpp/numeric/fenv
(примеров нет).
Это должно быть доступно в большинстве настольных/серверных сред С++ 11 и C99. Существуют также специфические для платформы варианты, которые предшествуют стандартизации всего этого.
Я бы предположил, что включение исключений делает код более медленным, поэтому, вероятно, по этой причине большинство платформ, о которых я знаю, отключает исключения по умолчанию.