Почему мы не можем использовать ‘==’ для сравнения двух чисел с плавающей запятой или двойных чисел
Я читаю «Эффективный java» Джошуа Блоха и в пункте 8: соблюдайте общий контракт при переопределении равных , это утверждение написано
для полей float используйте метод Float.compare; и для двойных полей используйте Double.compare. Специальная обработка плавающих и двойных полей необходима из-за существования Float.NaN, -0.0f и аналогичных двойных констант;
Может кто-нибудь объяснить мне пример, почему мы не можем использовать ==
для float или двойного сравнения
- Понимание работы equals и hashCode в HashMap
- Есть ли утилита отражения Java для глубокого сравнения двух объектов?
- Лучшая реализация для метода hashCode
- Hashcode и Equals для Hashset
- BigDecimal равно () по сравнению с compareTo ()
- Как проверить, равна ли моя строка нулевой?
- В чем разница между «.equals» и «==»?
- Какие проблемы следует учитывать при переопределении равных и hashCode в Java?
- Почему я должен переопределять hashCode (), когда я переопределяю метод equals ()?
- Переопределение GetHashCode для изменяемых объектов?
- Разность C # между == и Equals ()
- Это плохая идея, если equals (null) вместо NullPointerException выбрасывает?
- Как быстро проверить, имеют ли два объекта передачи данных равные свойства в C #?
Из apidoc, Float.compare
:
Сравнивает два указанных значения float. Знак возвращаемого целочисленного значения совпадает с признаком целого числа, которое будет возвращено вызовом:
новый Float (f1) .compareTo (новый Float (f2))
Float.compareTo
:
Численно сравнивает два объекта Float. Существует два способа сравнения результатов этого метода с теми, которые выполняются операторами численного сравнения языка Java (<, <=, ==,> =>) при применении к примитивным значениям float:
- Float.NaN считается этим методом равным себе и большим, чем все другие значения float (включая Float.POSITIVE_INFINITY).
- 0.0f считается этим методом больше -0.0f.
Это гарантирует, что естественный порядок объектов Float, налагаемых этим методом, согласуется с равными.
Рассмотрим следующий код:
System.out.println(-0.0f == 0.0f); //true System.out.println(Float.compare(-0.0f, 0.0f) == 0 ? true : false); //false System.out.println(Float.NaN == Float.NaN);//false System.out.println(Float.compare(Float.NaN, Float.NaN) == 0 ? true : false); //true System.out.println(-0.0d == 0.0d); //true System.out.println(Double.compare(-0.0d, 0.0d) == 0 ? true : false);//false System.out.println(Double.NaN == Double.NaN);//false System.out.println(Double.compare(Double.NaN, Double.NaN) == 0 ? true : false);//true
Вывод неправильный, так как то, что не является числом, просто не является числом и должно рассматриваться как равное с точки зрения сравнения чисел. Ясно также, что 0=-0
.
Давайте посмотрим, что делает Float.compare
:
public static int compare(float f1, float f2) { if (f1 < f2) return -1; // Neither val is NaN, thisVal is smaller if (f1 > f2) return 1; // Neither val is NaN, thisVal is larger int thisBits = Float.floatToIntBits(f1); int anotherBits = Float.floatToIntBits(f2); return (thisBits == anotherBits ? 0 : // Values are equal (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) 1)); // (0.0, -0.0) or (NaN, !NaN) }
Float.floatToIntBits
:
Возвращает представление заданного значения с плавающей запятой в соответствии с битовой маской «одиночный формат» с плавающей точкой IEEE 754. Бит 31 (бит, выбранный маской 0x80000000) представляет знак числа с плавающей запятой. Показатели экспоненты представляют собой биты 30-23 (биты, выбранные маской 0x7f800000). Биты 22-0 (биты, которые выбраны маской 0x007fffff) представляют значимые (иногда называемые мантиссой) числа с плавающей запятой.
Если аргумент является положительной бесконечностью, результат равен 0x7f800000.
Если аргумент отрицательной бесконечности, результат равен 0xff800000.
Если аргументом является NaN, результат равен 0x7fc00000.
Во всех случаях результатом является целое число, которое при задании методу intBitsToFloat (int) создает значение с плавающей запятой, такое же, как и аргумент floatToIntBits ( за исключением того, что все значения NaN сворачиваются до единственного «канонического» значения NaN ).
Из JLS 15.20.1. Операторы численного сравнения <, <=,> и> =
Результатом сравнения с плавающей запятой, определяемого спецификацией стандарта IEEE 754, является:
Если любой из операндов равен NaN, тогда результат будет ложным.
Все значения, отличные от NaN, упорядочены, причем отрицательная бесконечность меньше всех конечных значений и положительная бесконечность больше всех конечных значений.
Положительный нуль и отрицательный нуль считаются равными. Например, -0.0 <0.0 является ложным, но -0.0 <= 0.0 истинно.
Обратите внимание, однако, что методы Math.min и Math.max обрабатывают отрицательный нуль как строго меньший положительного нуля.
Для строгих сравнений, где операнды положительные ноль и отрицательные ноль, результат будет неправильным.
Из JLS 15.21.1. Операторы числового равенства == и! = :
Результатом сравнения с плавающей запятой, определяемого спецификацией стандарта IEEE 754, является:
Тестирование равенства с плавающей точкой выполняется в соответствии с правилами стандарта IEEE 754:
Если либо операнд NaN, то результат == является ложным, но результат! = Истинен. Действительно, тест x! = X истинен тогда и только тогда, когда значение x равно NaN. Методы Float.isNaN и Double.isNaN также могут использоваться для проверки того, является ли значение NaN.
Положительный нуль и отрицательный нуль считаются равными. Например, -0.0 == 0.0 истинно.
В противном случае два различных значения с плавающей запятой считаются неравными операторами равенства. В частности, существует одно значение, представляющее положительную бесконечность, а одно значение представляет отрицательную бесконечность; каждый сравнивает равный только с самим собой, и каждый сравнивает неравные со всеми другими значениями.
Для сравнений равенств, где оба операнда являются NaN, результат будет неправильным.
Поскольку полное упорядочение ( =
, <
, >
, <=
, >=
) используется многими важными алгоритмами (см. Все classы, реализующие интерфейс Comparable ), лучше использовать метод сравнения, поскольку он даст более последовательное поведение.
Следствием полного упорядочения в контексте стандарта IEEE-754 является разница между положительным и отрицательным нулями.
Например, если вы используете оператор равенства вместо метода сравнения и имеете некоторый набор значений, а ваша логика кода принимает некоторые решения, основанные на упорядочении элементов, и вы как-то начинаете получать излишки значений NaN, они будут все вместо этого следует рассматривать как разные значения как одни и те же значения.
Вероятно, это может привести к ошибке в поведении программы, пропорциональной количеству / скорости значений NaN. И если у вас много положительных и отрицательных нhive, это всего лишь одна пара, которая повлияет на вашу логику с ошибкой.
Float использует 32-битный формат IEEE-754, а Double использует 64-битный формат IEEE-754.
float
(и double
) имеют некоторые специальные битовые последовательности, зарезервированные для специальных значений, которые не являются «цифрами»:
- Отрицательная бесконечность, внутреннее представление
0xff800000
- Положительная бесконечность, внутреннее представление
0x7f800000
- Не число, внутреннее представление
0x7fc00000
Каждый из них возвращает 0
(что означает «то же самое») по сравнению с самим собой с использованием Float.compare()
, но следующие сравнения с использованием ==
отличаются от этого для Float.NaN
:
Float.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY // true Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY // true Float.NaN == Float.NaN // false
Поэтому при сравнении значений float
, чтобы быть согласованным для всех значений, включая специальное значение Float.compare()
оптимальным вариантом является Float.compare()
.
То же самое относится к double
.
Для сравнения объектов с плавающей запятой существует две причины:
- Я занимаюсь математикой, поэтому хочу сравнить их числовые значения. Численно -0 равно +0, а NaN не равно никому, даже самому себе, потому что «равно» – это свойство, которое имеет только числа, а NaN – не число.
- Я работаю с объектами в компьютере, поэтому мне нужно различать разные объекты и поместить их в порядок. Это необходимо для сортировки объектов в дереве или другом контейнере, например.
Оператор ==
обеспечивает математические сравнения. Он возвращает false для NaN == NaN
и true для -0.f == +0.f
Процедуры compare
и compareTo
обеспечивают сравнение объектов. При сравнении NaN с самим собой они указывают, что они одинаковы (возвращая нуль). При сравнении -0.f
с +0.f
они указывают, что они различны (возвращая ненулевое значение).