Подтвердить что ты не робот

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

Рассмотрим

#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 здесь.

4b9b3361

Ответ 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. Существуют также специфические для платформы варианты, которые предшествуют стандартизации всего этого.

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