Правильный способ сравнения чисел с плавающей точкой или чисел двойной точности в Java

Правильное сравнение float или сравнение double — это проблема, специфичная не только для Java. Сегодня ее можно наблюдать практически во всех языках программирования. В памяти компьютера float и double хранятся в стандартном формате IEEE 754. Как работает фактическое хранение и преобразование, выходит за рамки этой статьи.

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

Давайте узнаем, как сравнивать значения с плавающей точкой в Java.

1. Сравнение двойных чисел – Простое сравнение [Не рекомендуется]

Сначала посмотрите на простое сравнение, чтобы понять, что именно не так со сравнением double с оператором ==. В данной программе я создаю одно и то же число с плавающей точкой(т. е. 1.1) двумя способами:

  1. Прибавьте 0,1 11 раз.
  2. Умножьте 0,1 на 11.

Теоретически обе операции должны дать число 1,1. И когда мы сравним результаты обоих методов, они должны совпасть.

private static void simpleFloatsComparison(){//Method 1double f1 = .0;for(int i = 1; i <= 11; i++) {f1 += .1;}//Method 2double f2 = .1 * 11;System.out.println("f1 = " + f1);System.out.println("f2 = " + f2);if(f1 == f2)System.out.println("f1 and f2 are equal\n");elseSystem.out.println("f1 and f2 are not equal\n");}

Вывод программы.

f1 = 1.0999999999999999f2 = 1.1f1 and f2 are not equal

Посмотрите на оба значения, выведенные в консоли. f1 вычисляется как 1.0999999999999999. Это как раз та проблема, которую округление вызывает изнутри. Вот почему сравнение с плавающей точкой с оператором ‘==’ не рекомендуется.

 

2. Сравнение дважды – сравнение на основе порогового значения [рекомендуется]

Теперь, когда мы знаем проблему с оператором равенства, давайте решим ее. Используя программирование, мы не можем изменить способ хранения или вычисления этих чисел с плавающей точкой. Поэтому нам нужно адаптировать решение, в котором мы соглашаемся, что a определяет разницу в обоих значениях, которую мы можем допустить и по-прежнему считать числа равными. Эта согласованная разница в значениях называется порогом или эпсилоном.

Итак, чтобы использовать «сравнение чисел с плавающей точкой на основе порогового значения», мы можем использовать метод Math.abs() для вычисления разницы между двумя числами и сравнения разницы с пороговым значением.

private static void thresholdBasedFloatsComparison(){final double THRESHOLD = .0001;//Method 1double f1 = .0;for(int i = 1; i <= 11; i++) {f1 += .1;}//Method 2double f2 = .1 * 11;System.out.println("f1 = " + f1);System.out.println("f2 = " + f2);if(Math.abs(f1 - f2) < THRESHOLD)System.out.println("f1 and f2 are equal using threshold\n");elseSystem.out.println("f1 and f2 are not equal using threshold\n");}

Вывод программы.

f1 = 1.0999999999999999f2 = 1.1f1 and f2 are equal using threshold

 

3. Сравнение чисел типа double – Сравнение с BigDecimal [Рекомендуется]

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

Самое лучшее то, что числа BigDecimal неизменяемы, то есть если вы создадите BigDecimal BD со значением «1.23», этот объект останется «1.23» и никогда не сможет быть изменен. Этот класс предоставляет множество методов, которые можно использовать для выполнения числовых операций над его значением.

Вы можете использовать его метод compareTo() для сравнения с числами BigDecimal. Он игнорирует масштаб при сравнении.

а.сравнитьС(б);

Метод возвращает:

-1 – если а < б)

0 – если а == б

1 – если а > б

Никогда не используйте метод equals() для сравнения экземпляров BigDecimal. Это потому, что эта функция equals будет сравнивать масштаб. Если масштаб отличается, equals() вернет false, даже если математически они являются одним и тем же числом.

Программа Java для сравнения чисел двойной точности с классом BigDecimal.

private static void testBdEquality(){BigDecimal a = new BigDecimal("2.00");BigDecimal b = new BigDecimal("2.0");System.out.println(a.equals(b)); // falseSystem.out.println(a.compareTo(b) == 0); // true}

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

private static void bigDecimalComparison(){//Method 1BigDecimal f1 = new BigDecimal("0.0");BigDecimal pointOne = new BigDecimal("0.1");for(int i = 1; i <= 11; i++) {f1 = f1.add(pointOne);}//Method 2BigDecimal f2 = new BigDecimal("0.1");BigDecimal eleven = new BigDecimal("11");f2 = f2.multiply(eleven);System.out.println("f1 = " + f1);System.out.println("f2 = " + f2);if(f1.compareTo(f2) == 0)System.out.println("f1 and f2 are equal using BigDecimal\n");elseSystem.out.println("f1 and f2 are not equal using BigDecimal\n");}

Вывод программы.

f1 = 1.1f2 = 1.1f1 and f2 are equal using BigDecimal

Вот и все о сравнении чисел с плавающей точкой в Java. Поделитесь своими мыслями в разделе комментариев.

Прокрутить вверх