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

Почему мы не можем использовать '==' для сравнения двух чисел с плавающей точкой или двойных чисел

Я читаю "Эффективная java" Джошуа Блоха и в Пункт 8: выполняйте общий контракт при переопределении равных, этот оператор написан

для полей float, используйте метод Float.compare; и для двойных полей используйте Double.compare. Производится специальная обработка плавающих и двойных полей необходимо из существования Float.NaN, -0.0f и аналогичного двойного постоянные;

Может кто-нибудь объяснить мне пример, почему мы не можем использовать == для float или двойного сравнения

4b9b3361

Ответ 1

Из apidoc, Float.compare:

Сравнивает два указанных значения float. Знак возвращаемого целочисленного значения совпадает с признаком целого числа, которое будет возвращено вызовом:

new 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 - false, но -0.0 <= 0.0 истинно.

  • Обратите внимание, однако, что методы Math.min и Math.max обрабатывают отрицательный ноль как строго меньший положительного нуля.

Для строгих сравнений, где операнды являются положительными и отрицательными, результат будет неправильным.

Из JLS 15.21.1. Операторы числового равенства == и!=:

Результатом сравнения с плавающей запятой, определяемого спецификацией стандарта IEEE 754, является:

Тестирование равенства с плавающей точкой выполняется в соответствии с правилами стандарта IEEE 754:

  • Если либо операнд NaN, то результат == является ложным, но результат!= - true. Действительно, тест x!= X истинен тогда и только тогда, когда значение x равно NaN. Методы Float.isNaN и Double.isNaN также могут использоваться для проверки того, является ли значение NaN.

  • Положительный нуль и отрицательный нуль считаются равными. Например, -0.0 == 0.0 истинно.

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

Для сравнений равенств, где оба операнда NaN, результат будет неправильным.

Поскольку общее упорядочение (=, <, >, <=, >=)используется многими важными алгоритмами (см. все классы, реализующие интерфейс Comparable), лучше использовать метод сравнения, потому что это даст более последовательное поведение.

Следствием полного упорядочения в контексте стандарта IEEE-754 является разница между положительным и отрицательным нулями.

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

Это может привести к ошибке в поведении программы, пропорциональной количеству/скорости значений NaN. И если у вас много положительных и отрицательных нулей, то только одна пара влияет на вашу логику с ошибкой.

Float использует 32-битный формат IEEE-754 и Double использует 64-битный формат IEEE-754.

Ответ 2

floatdouble) имеют некоторые специальные битовые последовательности, зарезервированные для специальных значений, которые не являются "цифрами":

  • Отрицательная бесконечность, внутреннее представление 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.NaN, Float.compare() является наилучшим вариантом.

То же самое относится к double.

Ответ 3

Есть две причины для сравнения объектов с плавающей запятой:

  • Я занимаюсь математикой, поэтому хочу сравнить их числовые значения. Численно -0 равно +0, а NaN не равно никому, даже самому себе, потому что "равно" - это свойство, которое имеют только числа, а NaN - не число.
  • Я работаю с объектами на компьютере, поэтому мне нужно различать разные объекты и размещать их по порядку. Это необходимо для сортировки объектов в дереве или другом контейнере, например.

Оператор == обеспечивает математическое сравнение. Он возвращает false для NaN == NaN и true для -0.f == +0.f

Подпрограммы compare и compareTo предоставляют сравнения объектов. При сравнении NaN с самим собой они указывают, что они одинаковы (возвращая нуль). При сравнении -0.f с +0.f они указывают, что они различны (возвращая ненулевое значение).