Отказ от ответственности: я не знаю полного стандарта C ++, я немного изучил то, что было сказано о поплавках.Я знаю о IEEE 754-2008 и числах с плавающей запятой и C ++.
Да, вы правы, это неопределенное поведение по стандарту C ++ 17.
Краткое чтение:
Стандарт не говорит, что std::min(0.0, 1.0);
- это неопределенное поведение, он говорит, что constexpr const double& min(const double& a, const double& b);
- это неопределенное поведение.Это означает, что не применяется функция, которая не определена, это само объявление функции , которое не определено.Как и в случае с математикой: минимальная функция невозможна для полного диапазона чисел с плавающей точкой IEEE 754. Как вы отметили,
Но неопределенное поведение не обязательно означаетсбой или ошибка компиляции.Это просто означает, что он не определен стандартом C ++ и, в частности, говорит, что он может «вести себя во время перевода или выполнения программы задокументированным образом, характерным для среды»
Почему вы не должны использовать std::min
on double:
Поскольку я понимаю, что следующий раздел длинного чтения может стать скучным, вот забавный пример риска использования NaN внутри сравнений (я даже не пробовал сортировать алгоритмы…):
#include <iostream>
#include <cmath>
#include <algorithm>
int main(int, char**)
{
double one = 1.0, zero = 0.0, nan = std::nan("");
std::cout << "std::min(1.0, NaN) : " << std::min(one, nan) << std::endl;
std::cout << "std::min(NaN, 1.0) : " << std::min(nan, one) << std::endl;
std::cout << "std::min_element(1.0, 0.0, NaN) : " << std::min({one, zero, nan}) << std::endl;
std::cout << "std::min_element(NaN, 1.0, 0.0) : " << std::min({nan, one, zero}) << std::endl;
std::cout << "std::min(0.0, -0.0) : " << std::min(zero, -zero) << std::endl;
std::cout << "std::min(-0.0, 0.0) : " << std::min(-zero, zero) << std::endl;
}
При компиляции на моем macbookpro с Apple LLVM версии 10.0.0 (clang-1000.10.44.4) (я делаю точность, потому что, это неопределенное поведение, так что этоможет теоретически иметь другие результаты на других компиляторах) Я получаю:
$ g++ --std=c++17 ./test.cpp
$ ./a.out
std::min(1.0, NaN) : 1
std::min(NaN, 1.0) : nan
std::min_element(1.0, 0.0, NaN) : 0
std::min_element(NaN, 1.0, 0.0) : nan
std::min(0.0, -0.0) : 0
std::min(-0.0, 0.0) : -0
Это означает, что вопреки тому, что вы можете предположить, std::min
не является симметричным , когда задействованы NaN, иличетный -0.0
.И NaNs не размножаются.Короткая история: Это вызвало у меня некоторую боль в предыдущем проекте, где мне пришлось реализовать собственную функцию min
для правильного распространения NaN с обеих сторон, как того требовала спецификация проекта.Поскольку std::min
для двойных чисел не определено !
IEEE 754:
Как вы заметили, числа IEEE 754 с плавающей запятой (или ISO / IEC / IEEE 60559: 2011-06, который является нормой, используемой стандартом C11 (см. ниже, где более или менее копируются IEEE754 для языка C), не имеет строгого слабого порядка, поскольку NaNs нарушает транзитивность несопоставимости ( четвертый пункт страницы Википедии )
Самое интересное, что норма IEE754 была пересмотрена в 2008 году (теперь она называется IEEE-754-2008), которая включает в себя общую функцию заказа .Дело в том, что и C ++ 17, и C11 не реализуют IEE754-2008, а скорее ISO / IEC / IEEE 60559: 2011-06
Но кто знает?Возможно, это изменится в будущем.
Длинное чтение:
Во-первых, давайте начнем с напоминания того, что на самом деле является неопределенным поведением, из того же стандартного черновика, который высвязанный (выделено мной):
неопределенное поведение, для которого данный документ не предъявляет никаких требований
[Примечание 1 к записи: Неопределенное поведение может ожидаться приэтот документ опускает любое явное определение поведения или когда программа использует ошибочную конструкцию или ошибочные данные.Допустимое неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей или без выдачи диагностического сообщения), до прекращения переводаили выполнение (с выдачей диагностического сообщения).Многие ошибочные программные конструкции не порождают неопределенное поведение;они должны быть диагностированы.Оценка константного выражения никогда не демонстрирует поведение, явно указанное как неопределенное в пунктах с 4 по 14 данного документа (7.7).—Конечная записка]
Нет такой вещи, как «уступать» неопределенному поведению.Это просто то, что не определено в стандарте C ++.Это может означать, что вы можете использовать его и получить правильный результат на свой страх и риск (например, выполнив std::min(0.0, 1.0);
Или это может вызвать предупреждение или даже ошибки компиляции, если вы найдете компилятор, который действительно осторожен с числами с плавающей запятой!
О подмножестве ... Вы говорите:
Я не вижу ничего в Стандарте (довольно большом, 1719 страниц, и, следовательно, этот вопрос и тег языка-юриста), который математически приводитк выводу, что std :: min подходит для удвоений при условии, что NaN не задействованы.
Я тоже не читал стандарт, но из той части, которую вы опубликовали, кажется, стандарт ужеговорит, что это нормально. Я имею в виду, если вы создадите новый тип T , который оборачивает двойные значения, исключая NaN, тогда определение template<class T> constexpr const T& min(const T& a, const T& b);
, примененное к вашему новому типу , будет иметь определенныйповедение и вести себя точно так, как вы ожидаете от минимальной функции.
Мы также могли бы взглянуть на стандартное определение операции <
ondouble
, который определен в разделе 25.8 Математические функции для типов с плавающей запятой , что говорит не очень полезно:
Функции классификации / сравнения ведут себя так же, какМакросы C с соответствующими именами, определенными в стандартной библиотеке C.Каждая функция перегружена для трех типов с плавающей точкой.См. Также: ISO C 7.12.3, 7.12.4
Что говорит стандарт C11 ?(Поскольку я предполагаю, что C ++ 17 не использует C18)
Операторы отношения и равенства поддерживают обычные математические отношения между числовыми значениями.Для любой упорядоченной пары числовых значений верно одно из отношений - меньше, больше и равно - верно.Реляционные операторы могут вызывать «недопустимое» исключение с плавающей запятой, когда значения аргумента равны NaN.Для NaN и числового значения, или для двух NaN, верно только неупорядоченное отношение.241)
Что касается использования нормы C11, то оно находится в приложении F этой нормы:
В этом приложении указана поддержка языка C для стандарта IEC 60559 с плавающей запятой.Стандарт МЭК 60559 - это, в частности, двоичная арифметика с плавающей точкой для микропроцессорных систем, второе издание (МЭК 60559: 1989), ранее обозначенная МЭК 559: 1989 и в качестве стандарта IEEE для двоичной арифметики с плавающей точкой (ANSI / IEEE 754−1985).Стандарт IEEE для радикально-независимой арифметики с плавающей точкой (ANSI / IEEE854-1987) обобщает бинарный стандарт для удаления зависимостей от оснований и длины слова.МЭК 60559, как правило, относится к стандарту с плавающей запятой, как при работе с МЭК 60559, формате МЭК 60559 и т. Д.