Сравнение чисел с плавающей точкой в 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 должно быть основано на контексте задачи
Почему использование ==
для float небезопасно
Оператор ==
в Java сравнивает числа на уровне бит, что может стать причиной ошибок при обработке чисел с плавающей точкой из-за округлений и приближений. Например, число 0.1
в двоичной системе не имеет точного эквивалента, что вызывает неправильные результаты при сравнении.
Также важно обосновано подходить к выбору величины эпсилон, отталкиваясь от точности, необходимой для решаемой задачи.
Ситуации, в которых эпсилон спасает:
Использование эпсилон требуется при:
- Выполнение сложных математических вычислений
- Работа с геометрическими алгоритмами, позволяющими малые расхождения
- Анализ результатов научных симуляций
- Проведение финансовых расчётов, когда мелкая разница несущественна
Различные способы сравнения
Существуют другие методы сравнения чисел с плавающей точкой:
- Обертывающие классы и метод
equals()
для строгих сравнений - Методы
Float.compare()
иDouble.compare()
, учитывающие специальные значения, такие как NaN и бесконечности
Визуализация
Сравнение чисел с плавающей точкой методом ==
можно сопоставить с попыткой поместить необычные камни (float'ы) в квадратные отверстия (==
).
Размер камней (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
из-за двоичного округления.
Сравнение чисел с плавающей точкой: подводные камни
Сравнение чисел различных типов не всегда тривиально, также важно учесть, что машинный эпсилон зависит от системы и используемого алгоритма.
Точность против производительности
Иногда необходимо выбирать между точностью и быстродействием. Использование эпсилон слегка увеличивает нагрузку, но позволяет настроить требуемую в конкретной системе точность.
Полезные материалы
- Официальная документация Oracle по Java и арифметике чисел с плавающей точкой.
- Стандарт IEEE 754, который лежит в основе представления чисел с плавающей точкой.
- Обсуждение на Stack Overflow о том, почему для представления валют является нежелательным использование типов double или float.
- Руководство от Oracle по примитивным типам данных в Java.
- Обсуждение на Stack Overflow методов сравнения значений класса double.
- Статья на Википедии о методах минимизации ошибок, связанных с числами с плавающей точкой.