Я был членом комитета IEEE-754, я постараюсь помочь немного прояснить ситуацию.
Прежде всего, числа с плавающей точкой не являются действительными числами, а арифметика с плавающей точкой не удовлетворяет аксиомам реальной арифметики. Трихотомия - не единственное свойство реальной арифметики, которое не распространяется на поплавки и даже не является самым важным. Например:
- Дополнение не ассоциативно.
- Распределительный закон не имеет места.
- Есть числа с плавающей точкой без инверсий.
Я мог бы продолжить. Невозможно указать арифметический тип фиксированного размера, который удовлетворяет всем свойствам реальной арифметики, которые мы знаем и любим. Комитет 754 должен решить согнуть или сломать некоторые из них. Это руководствуется некоторыми довольно простыми принципами:
- Когда мы можем, мы сопоставляем поведение реальной арифметики.
- Когда мы не можем, мы стараемся сделать нарушения максимально предсказуемыми и максимально простыми для диагностики.
Что касается вашего комментария «это не значит, что правильный ответ ложный», это неправильно. Предикат (y < x)
спрашивает, y
меньше ли x
. Если y
равно NaN, то оно не меньше, чем любое значение с плавающей запятой x
, поэтому ответ обязательно ложный.
Я упомянул, что трихотомия не выполняется для значений с плавающей точкой. Тем не менее, есть аналогичное свойство, которое имеет место. Пункт 5.11, пункт 2 стандарта 754-2008:
Возможны четыре взаимоисключающих отношения: меньше, равно, больше и неупорядочено. Последний случай возникает, когда хотя бы один операнд является NaN. Каждый NaN сравнивается неупорядоченным со всем, включая себя.
Что касается написания дополнительного кода для обработки NaN, обычно возможно (хотя и не всегда легко) структурировать ваш код таким образом, чтобы NaN проваливались должным образом, но это не всегда так. Когда это не так, может потребоваться дополнительный код, но это небольшая цена за удобство, которое алгебраическое замыкание принесло арифметике с плавающей точкой.
Приложение: Многие комментаторы утверждают, что было бы более полезно сохранить рефлексивность равенства и трихотомии на том основании, что принятие NaN! = NaN, похоже, не сохраняет какой-либо знакомой аксиомы. Признаюсь, что сочувствую этой точке зрения, поэтому я подумал, что вернусь к этому ответу и предоставлю немного больше контекста.
Насколько я понимаю из разговора с Каханом, NaN! = NaN возникла из двух прагматических соображений:
Это x == y
должно быть эквивалентно, x - y == 0
когда это возможно (помимо теоремы о реальной арифметике, это делает аппаратную реализацию сравнения более компактной, что было крайне важно во время разработки стандарта - однако следует отметить, что это нарушается для x = y = бесконечность, так что это не очень хорошая причина сама по себе; ее вполне можно было бы склонить (x - y == 0) or (x and y are both NaN)
).
Что еще более важно, не было isnan( )
предиката в то время, когда NaN был формализован в арифметике 8087; было необходимо предоставить программистам удобные и эффективные средства обнаружения значений NaN, которые не зависели бы от языков программирования, обеспечивающих что-то подобное, isnan( )
что может занять много лет. Я процитирую собственное письмо Кахана на эту тему:
Если бы не было способа избавиться от NaN, они были бы такими же бесполезными, как Indefinites на CRAY; как только они встретятся, вычисление лучше остановить, чем продолжать в течение неопределенного времени до неопределенного завершения. Вот почему некоторые операции с NaN должны давать результаты, отличные от NaN. Какие операции? … Исключение составляют предикаты C «x == x» и «x! = X», которые соответственно равны 1 и 0 для каждого бесконечного или конечного числа x, но обращаются, если x не является числом (NaN); они обеспечивают единственное простое исключительное различие между NaN и числами в языках, в которых отсутствует слово для NaN и предикат IsNaN (x).
Обратите внимание, что это также логика, которая исключает возвращение чего-то вроде «Not-A-Boolean». Возможно, этот прагматизм был неуместен, и стандарт должен был это потребовать isnan( )
, но это сделало бы практически невозможным эффективное и удобное использование NaN в течение нескольких лет, пока мир ждал принятия языка программирования. Я не уверен, что это был бы разумный компромисс.
Чтобы быть тупым: результат NaN == NaN не изменится сейчас. Лучше научиться жить с этим, чем жаловаться в интернете. Если вы хотите утверждать, что отношение порядка, подходящее для контейнеров, также должно существовать, я бы рекомендовал рекомендовать вашему любимому языку программирования реализовать totalOrder
предикат, стандартизированный в IEEE-754 (2008). Тот факт, что это еще не говорит об обоснованности озабоченности Кахана, которая мотивировала текущее положение дел.
while (fabs(x - oldX) > threshold)
выхода из цикла, если происходит сходимость или NaN входит в вычисление. Обнаружение NaN и соответствующих средств защиты будет происходить за пределами цикла.