Сравнение чисел с плавающей точкой в Java: проблема ==

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Сравнивать числа с плавающей точкой в Java посредством == опасно из-за риска возникновения неточности, обусловленной особенностями бинарного их представления. Безопаснее сравнивать не сами числа, а модуль их разности с неким допустимым значением ошибки, или эпсилоном.

Пример:

Java
Скопировать код
public static boolean closeEnough(float a, float b) {
    final float EPSILON = 1E-6f;
    return Math.abs(a – b) < EPSILON;
}

// Пример использования
System.out.println(closeEnough(0.2f, (0.2f + 0.1f – 0.1f))); // Есть вероятность вывода "true", если разница в пределах допустимой погрешности

Основные моменты:

  • В реальности проблемы точности могут подстерегать вас
  • Для учёта округления используйте допуск, или EPSILON
  • Паттерн Math.abs(a – b) < EPSILON повинен стать практикой
  • Значение для EPSILON должно быть основано на контексте задачи
Кинга Идем в IT: пошаговый план для смены профессии

Почему использование == для float небезопасно

Оператор == в Java сравнивает числа на уровне бит, что может стать причиной ошибок при обработке чисел с плавающей точкой из-за округлений и приближений. Например, число 0.1 в двоичной системе не имеет точного эквивалента, что вызывает неправильные результаты при сравнении.

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

Ситуации, в которых эпсилон спасает:

Использование эпсилон требуется при:

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

Различные способы сравнения

Существуют другие методы сравнения чисел с плавающей точкой:

  • Обертывающие классы и метод equals() для строгих сравнений
  • Методы Float.compare() и Double.compare(), учитывающие специальные значения, такие как NaN и бесконечности

Визуализация

Сравнение чисел с плавающей точкой методом == можно сопоставить с попыткой поместить необычные камни (float'ы) в квадратные отверстия (==).

Markdown
Скопировать код
Размер камней (float):           5.15, 5.2, 5.25, 5.3
Форма отверстия (==):            5.2

| Попытка поместить в отверстие | Успешно? |
|-----------------------|---------|
| 5.15                  | ❌     |
| 5.2                   | ✅     |
| 5.25                  | ❌     |
| 5.3                   | ❌     |

Необычная форма камня — это неточность чисел с плавающей точкой, а квадратное отверстие == идеально для целых чисел.

Десятичные и двоичные числа: от любви до непонимания

Числа с плавающей точкой представлены в двоичной форме с использованием научной нотации, что ведет к приближенному представлению. Например, сумма 0.1 + 0.2 не равна 0.3 из-за двоичного округления.

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

Сравнение чисел различных типов не всегда тривиально, также важно учесть, что машинный эпсилон зависит от системы и используемого алгоритма.

Точность против производительности

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

Полезные материалы

  1. Официальная документация Oracle по Java и арифметике чисел с плавающей точкой.
  2. Стандарт IEEE 754, который лежит в основе представления чисел с плавающей точкой.
  3. Обсуждение на Stack Overflow о том, почему для представления валют является нежелательным использование типов double или float.
  4. Руководство от Oracle по примитивным типам данных в Java.
  5. Обсуждение на Stack Overflow методов сравнения значений класса double.
  6. Статья на Википедии о методах минимизации ошибок, связанных с числами с плавающей точкой.