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

Почему бесконечности с плавающей точкой, в отличие от NaNs, равны?

Почему сравнение бесконечности не следует логике, применяемой к NaN? Этот код печатает false три раза:

double a = Double.NaN;
double b = Double.NaN;
System.out.println(a == b); // false
System.out.println(a < b); //  false
System.out.println(a > b); //  false

Однако, если я изменяю Double.NaN на Double.POSITIVE_INFINITY, я получаю true для равенства, но false для сравнений больше и меньше:

double a = Double.POSITIVE_INFINITY;
double b = Double.POSITIVE_INFINITY;
System.out.println(a == b); // true
System.out.println(a < b); //  false
System.out.println(a > b); //  false

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

4b9b3361

Ответ 1

Ваше рассуждение состоит в том, что Double.POSITIVE_INFINITY не должно быть равным самому себе, потому что оно "вероятно" было получено в результате потери точности.

Эта строка рассуждений применяется ко всем плавающим точкам. Любое конечное значение может быть получено в результате неточной операции. Это не помогло комитету по стандартизации IEEE 754 определить ==, как всегда, оценивая значение false для конечных значений, поэтому почему бесконечности должны быть разными?

Как определено, == полезно для людей, которые понимают, что он делает (т.е. проверять полученные значения с плавающей запятой, и, конечно, не значения, которые должны были быть получены с реальными вычислениями). Для тех, кто это понимает, и вам нужно понимать, что для использования с плавающей точкой даже для вычислений, которые не связаны с бесконечностью, удобно использовать Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY для оценки true, если только для проверки, является ли результат с плавающей запятой плавающей точкой, точечное вычисление Double.POSITIVE_INFINITY.

Это оставляет вопрос о том, почему NaN может позволить себе иметь особое поведение, а бесконечности должны следовать тем же общим принципам, что и конечные. NaN отличается от бесконечностей: основополагающим принципом стандарта IEEE 754 является то, что значения являются именно тем, чем они являются, но результат операции может быть аппроксимирован по отношению к реальному результату, и в этом случае полученное значение с плавающей запятой получается в соответствии с режимом округления.

Забудьте на мгновение, что 1.0 / 0.0 определяется как + inf, что является досадой в этом обсуждении. Подумайте на момент Double.POSITIVE_INFINITY только в результате операций, таких как 1.0e100 / 1.0e-300 или Double.MAX_VALUE + Double.MAX_VALUE. Для этих операций + inf является ближайшим приближением реального результата, как и для операций, которые приводят к конечному результату. Напротив, NaN - результат, который вы получаете, когда операция не имеет смысла. Защитно, что NaN ведет себя специально, но inf - это просто приближение всех значений, слишком больших для представления.

В действительности, 1.0 / 0.0 также создает + inf, но , что следует рассматривать как исключение. Было бы так же согласовано определить результат этой операции как NaN, но определение ее как + inf было более удобным в реализации некоторых алгоритмов. Пример представлен на странице 10 в заметках Kahan. Более подробная информация, чем большинство из них пожелает, содержится в статье "Отделы ветки для сложных элементарных функций или много шума из ничего" .. Я бы также интерпретировал существование в IEEE 754 флага "деление на ноль", отдельно от флага NaN, как признание того, что пользователь может захотеть обработать деление на ноль специально, хотя он не определен как производящий NaN.

Ответ 2

Потому что это стандарт. Infinity представляет число, большее или меньшее, чем Double.MAX_VALUE/-Double.MAX_VALUE.

NaN представляет собой результат операции, которая не имеет смысла. То есть операция не выходила с номером.

Я бы предположил, что логика - это когда число становится достаточно большим (бесконечность) и из-за ограничения чисел с плавающей запятой, добавление чисел к нему не изменит результат, поэтому его "как" бесконечность.

Итак, если вы хотите сравнить с действительно большими цифрами, в какой-то момент вы можете просто сказать, что эти два больших числа достаточно близки для всех целей и целей. Но если вы хотите сравнить две вещи, которые не являются цифрами, вы не можете сравнивать их так, чтобы они были ложными. По крайней мере, вы не могли сравнить их как примитив.

Ответ 3

Почему бесконечности равны? Потому что он работает.

Арифметика с плавающей точкой предназначена для создания (относительно) быстрых вычислений, которые сохраняют ошибки. Идея состоит в том, что вы не проверяете переполнение или другую бессмыслицу во время длительных вычислений; вы ждете, пока не закончите. Вот почему NaNs распространяют то, как они делают: как только вы получили NaN, вы можете сделать очень мало, что заставит его уйти. Как только вычисление будет закончено, вы можете найти NaN для проверки того, что-то пошло не так.

То же самое для бесконечностей: если есть возможность переполнения, не делайте того, что выбросит бесконечности.

Если вы хотите идти медленно и безопасно, IEEE-754 имеет механизмы для установки обработчиков ловушек для обеспечения обратных вызовов в ваш код, когда результатом вычисления будет NaN или бесконечность. В основном это не используется; это обычно слишком медленно и бессмысленно, как только код был должным образом отлажен (не так просто: люди получают PhD в том, как это делать хорошо).

Ответ 4

Другая перспектива, которая оправдывает равные "бесконечные" значения, заключается в том, чтобы вообще избегать cardinality. По существу, если вы не можете размышлять над тем, "как бесконечно сравнивается значение с другим, учитывая, что оба они бесконечны", проще предположить, что Inf = Inf.

Изменить: как пояснение моего комментария относительно мощности. Я приведу два примера относительно сравнения (или равенства) бесконечных величин.

Рассмотрим множество натуральных чисел S1 = {1,2,3, ...}, которое бесконечно. Также рассмотрим множество четных целых чисел S2 = {2,4,6, ...}, которые также бесконечны. Хотя в S1, как и в S2, явно есть вдвое больше элементов, у них есть "одинаково много" элементов, так как вы можете легко иметь взаимно однозначную функцию между наборами, т.е. 1 -> 2, 2-> 4,... Они имеют такую ​​же мощность.

Рассмотрим вместо этого множество действительных чисел R и множество целых чисел I. И снова оба являются бесконечными множествами. Однако для каждого целого числа I существует бесконечно много действительных чисел между (i, i+1). Таким образом, никакая взаимно однозначная функция не может отображать элементы этих двух множеств, и, следовательно, их мощность различна.

Bottomline: равенство бесконечных величин сложнее, проще его избежать на императивных языках:)

Ответ 5

Мне кажется, что "потому что он должен вести себя так же, как ноль", будет хорошим ответом. Арифметическое переполнение и нижнее течение должны быть схожими.

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

Если вы переполняете из самого большого почти бесконечно большого значения, которое может быть сохранено в поплавке, вы получаете INF, а INFs сравниваются как идентичные.

Это означает, что код, который обрабатывает числа, выходящие за пределы области действия в обоих направлениях, не потребует отдельного специального корпуса для того или иного. Вместо этого либо обоим, либо ни одному из них не придется обращаться по-разному.

И самое простое требование покрывается случаем "ни": вы хотите проверить, что что-то переполнено/выровнено, вы можете сравнить его с нулем /INF, используя только обычные операторы арифметического сравнения, не требуя, чтобы вы знали ваш текущий язык особый синтаксис команды проверки: это Math.isInfinite(), Float.checkForPositiveInfinity(), hasOverflowed()...?

Ответ 6

Правильный ответ простой: " стандарт docs). Но я не собираюсь быть циничным, потому что очевидно, что не то, что вам нужно.


В дополнение к другим ответам здесь я попытаюсь связать бесконечности с насыщающей арифметикой.

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

Скажем, у меня есть насыщающее целое число, представляющее оттенки серого. Почему я использую насыщающую арифметику? Потому что что-то ярче белого цвета по-прежнему белое, а все темнее черного, все еще черное (кроме orange). Это означает BLACK - x == BLACK и WHITE + x == WHITE. Имеет смысл?

Теперь предположим, что мы хотим представить цвета в оттенках серого с (подписанным) 1s дополнением 8-битовым целым, где BLACK == -127 и WHITE == 127. Почему 1s дополняют? Потому что он дает нам подписанный нуль, например IEEE 754 с плавающей запятой, И поскольку мы используем насыщающую арифметику, -127 - x == -127 и 127 + x == 127.

Как это связано с бесконечностями с плавающей точкой? Замените целое число плавающей точкой, BLACK на NEGATIVE_INFINITY и WHITE на POSITIVE_INFINITY и что вы получите? NEGATIVE_INFINITY - x == NEGATIVE_INFINITY и POSITIVE_INFINITY + x == POSITIVE_INFINITY.

Поскольку вы использовали POSITIVE_INFINITY, я также его использую. Сначала нам нужен класс для представления нашего насыщающего целочисленного цвета; позвоните ему SaturatedColor и предположим, что он работает как любое другое целое число в Java. Теперь давайте возьмем ваш код и заменим double на наши собственные SaturatedColor и Double.POSITIVE_INFINITY на SaturatedColor.WHITE:

SaturatedColor a = SaturatedColor.WHITE;
SaturatedColor b = SaturatedColor.WHITE;

Как мы установили выше, SaturatedColor.WHITE (только WHITE выше) есть 127, поэтому сделаем это здесь:

SaturatedColor a = 127;
SaturatedColor b = 127;

Теперь мы используем операторы System.out.println, которые вы использовали, и замените a и b своим значением (значения?):

System.out.println(127 == 127);
System.out.println(127 < 127);
System.out.println(127 > 127);

Должно быть очевидно, что это будет печатать.

Ответ 7

Так как упоминалось Double.Nan.equals(Double.NaN): Это одно, что должно произойти, когда вы выполняете арифметику и сравниваете числа, это совершенно другая вещь, когда вы рассматриваете, как должны вести себя объекты.

Два типичных проблемных случая: Сортировка массива чисел и использование хэш-значений для реализации словарей, наборов и т.д. Существуют два исключительных случая, когда нормальное упорядочение с <, = и > не применяется: один случай состоит в том, что +0 = -0, а другой - NaN ≠ NaN и x < NaN, x > NaN, x = NaN всегда будет ложным, что бы ни было x.

Алгоритмы сортировки могут попасть в проблему с этим. Алгоритм сортировки может предполагать, что x = x всегда истинно. Поэтому, если я знаю, что x хранится в массиве и ищет его, я, возможно, не делаю никаких проверок границ, потому что поиск его должен найти что-то. Нет, если x является NaN. Алгоритм сортировки может предполагать, что точно один из < b и a >= b должны быть истинными. Нет, если один из NaN. Таким образом, наивный алгоритм сортировки может произойти сбой при наличии NaN. Вам нужно будет решить, где вы хотите, чтобы NaN закончили при сортировке массива, а затем измените код сравнения, чтобы он работал.

Теперь словари и наборы и вообще хеширование: что, если я использую NaN в качестве ключа? Набор содержит уникальные объекты. Если набор содержит NaN, и я пытаюсь добавить еще один, уникален ли он, потому что он не равен тому, который уже существует? Что относительно +0 и -0, должны ли они считаться равными или разными? Там правило, что любые два элемента, считающиеся равными, должны иметь одно и то же значение хэш-функции. Поэтому разумным является (возможно), что хеш-функция возвращает одно уникальное значение для всех NaN и одно уникальное значение для +0 и -0. И после поиска хэша, когда вам нужно найти элемент с тем же самым значением хэша, который фактически равен, два NaN следует считать равными (но отличными от чего-либо еще).

Вероятно, почему Double.Nan.equal() ведет себя отличным от ==.

Ответ 8

Это потому, что NaN не является числом и поэтому не равно любому числу, включая NaN.