1. Обзор
В этом уроке мы поговорим о различных способах сравнения двойных значений в Java. В частности, это не так просто, как сравнение других примитивных типов. На самом деле, это проблематично во многих других языках, а не только в Java.
Во-первых, мы объясним, почему использование простого является неточным и может привести к трудностям отслеживания ошибок во время выполнения. Затем мы покажем, как правильно сравнивать двойники в простой Java и распространенных сторонних библиотеках.
2. Использование
Неточность при сравнении с использованием метода вызвана тем, как двойные значения хранятся в памяти компьютера. Мы должны помнить, что существует бесконечное количество значений, которые должны поместиться в ограниченном пространстве памяти, обычно 64 бита. В результате мы не можем иметь точное представление большинства двойных значений в наших компьютерах . Они должны быть округлены, чтобы быть сохранены .
Из-за неточности округления могут возникнуть интересные ошибки:
double d1 = 0; for (int i = 1; i <= 8; i++) { d1 += 0.1; } double d2 = 0.1 * 8; System.out.println(d1); System.out.println(d2);
Обе переменные, d1 и d2, должны быть равны 0,8. Однако, когда мы запустим приведенный выше код, мы увидим следующие результаты:
0.7999999999999999 0.8
В этом случае сравнение обоих значений с результатом приведет к неправильному результату. По этой причине мы должны использовать более сложный алгоритм сравнения.
Если мы хотим иметь наилучшую точность и контроль над механизмом округления, мы можем использовать класс java.math.BigDecimal.
3. Сравнение двойников в простой Java
Рекомендуемым алгоритмом для сравнения двойных значений в простой Java является метод порогового сравнения . В этом случае нам нужно проверить, находится ли разница между обоими числами в пределах указанного допуска, обычно называемого эпсилон :
double epsilon = 0.000001d; assertThat(Math.abs(d1 - d2) < epsilon).isTrue();
Чем меньше значение эпсилона, тем выше точность сравнения. Однако, если мы зададим слишком малое значение допуска, мы получим тот же ложный результат, что и в простом. В общем, значение эпсилона с 5 и 6 десятичными знаками обычно является хорошим местом для начала .
К сожалению, в стандартном JDK нет утилиты, которую мы могли бы использовать для сравнения двойных значений рекомендуемым и точным способом. К счастью, нам не нужно писать его самим. Мы можем использовать различные специальные методы, предоставляемые бесплатными и широко известными сторонними библиотеками.
4. Использование Apache Commons Math
Apache Commons Math -одна из крупнейших библиотек с открытым исходным кодом, посвященных компонентам математики и статистики. Из множества различных классов и методов мы сосредоточимся на org.apache.commons.math3.util.Точность класс в частности. Он содержит 2 полезных equals() метода для правильного сравнения двойных значений :
double epsilon = 0.000001d; assertThat(Precision.equals(d1, d2, epsilon)).isTrue(); assertThat(Precision.equals(d1, d2)).isTrue();
Переменная epsilon , используемая здесь, имеет то же значение, что и в предыдущем примере. Это величина допустимой абсолютной ошибки. Однако это не единственное сходство с пороговым алгоритмом. В частности, оба метода equals используют один и тот же подход под капотом.
Версия функции с двумя аргументами-это просто ярлык для вызова метода equals(d1, d2, 1)|/. Значение эпсилона в этой версии довольно высокое. Поэтому мы не должны использовать его и всегда указывать значение допуска самостоятельно.
5. Использование Гуавы
Google Guava – это большой набор основных библиотек Java, которые расширяют стандартные возможности JDK. Он содержит большое количество полезных математических элементов в пакете com.google.common.math . Чтобы правильно сравнить двойные значения в Guava, давайте реализуем метод fuzzyEquals() из класса Double Math |:
double epsilon = 0.000001d; assertThat(DoubleMath.fuzzyEquals(d1, d2, epsilon)).isTrue();
Имя метода отличается от названия в математике Apache Commons, но под капотом он работает практически одинаково. Единственное отличие заключается в том, что нет перегруженного метода со значением по умолчанию epsilon.
6. Использование JUnit
JUnit является одним из наиболее широко используемых фреймворков модульного тестирования для Java. В общем, каждый модульный тест обычно заканчивается анализом разницы между ожидаемыми и фактическими значениями. Поэтому структура тестирования должна иметь правильные и точные алгоритмы сравнения. Фактически, JUnit предоставляет набор методов сравнения для общих объектов, коллекций и примитивных типов, включая специальные методы для проверки равенства двойных значений:
double epsilon = 0.000001d; assertEquals(d1, d2, epsilon);
На самом деле, он работает так же, как методы Guava и Apache Commons, описанные ранее.
Важно отметить, что существует также устаревшая версия с двумя аргументами без аргумента epsilon. Однако, если мы хотим быть уверены, что наши результаты всегда верны, мы должны придерживаться версии с тремя аргументами.
7. Заключение
В этой статье мы рассмотрели различные способы сравнения двойных значений в Java.
Мы объяснили, почему простое сравнение может привести к трудностям отслеживания ошибок во время выполнения. Затем мы показали, как правильно сравнивать значения в обычной Java и общих библиотеках.
Как всегда, исходный код примеров можно найти на GitHub .