При сравнении двух вещественных чисел получаем разный результат

Сравнить два вещественных числа между собой возможно лишь с некоторой степенью точности. Если два числа отличаются на малое значение, то можно считать их равными. Необходимую степень точности мы можем задать сами.

Нужно сравнить два числа и проверить равны ли они с точностью до одной миллионной (0.000001).

Код будет выглядеть следующим образом:

public class Main {
    public static void main(String[] args) {
      Scanner console = new Scanner(System.in);
      double a = console.nextDouble();
      double b = console.nextDouble();
      System.out.print(Math.abs(a-b) < 0.000001 ? "equal" : "not equal");
  }
}

если a = 12.000001, b = 12.000002 то на выходе получим not equal. Логично, ведь у нас строгий знак неравенства.

однако при значениях a = 12.000000, b = 12.000001 компилятор выдает equal, т.е считает что они равны.

Почему так происходит?


Ответы (2 шт):

Автор решения: Alexey Ten

Ноги растут всё из того же IEEE 754

Числа a = 12.000001 и b = 12.000002 не представимы точно и на самом деле

a = 12.0000009999999992515995472786016762256622314453125
b = 12.0000020000000002795559339574538171291351318359375

Как видите, a немножко меньше исходного числа, а b немножко больше, и этой неточности достаточно что выполнялись неравенства

a - 12 = 0.0000009999999992515995472786016762256622314453125 < 0.000001
b - a  = 0.000001000000001027956386678852140903472900390625  > 0.000001
→ Ссылка
Автор решения: Serge3leo

Как в том анекдоте про академика Струве в Пулково, за "вычислитель должен быть девиц". Построим неформальную модель.

Что такое a и b? Это результат форматного преобразования входных чисел, т.е. a = a₁ ± Δ₁, b = b₂ ± Δ₂, где Δ₁ и Δ₂ не превосходят Math.ulp(a)/2 и Math.ulp(b)/2, соответственно.

Как работает Math.abs(a-b)? Результатом является |a-b| ± Δ₃.

Что такое литерал 0.000001? Он равняется 0.000001 ± Δ₄.

Таким образом, Math.abs(a-b) < 0.000001 сработает если |a₁-b₂| ± (Δ₁ + Δ₂) < 0.000001 ± (Δ₃ + Δ₄)

В случае, когда Δ₁, Δ₂, Δ₃, Δ₄, влияют на результат, т.е. в случае a ≈ b и |a-b| ≈ 0.000001, Δ₁ + Δ₂ ≲ Math.ulp((a + b)/2)Math.ulp(a), а Δ₃ + Δ₄ ≲ Math.ulp(0.000001).

Теперь что такое сравнить и напечатать? Здесь у нас два варианта, либо печатать три сообщения: "равны", "различны" и "кто его знает", либо гарантировать один случай - "equal", а область неопределённости отнести к его отрицанию - "not equal".

И тогда выражение, которое гарантирует, что "equal" выдаётся только тогда, когда гарантировано |a₁-b₂| < 0.000001, может выглядеть так:

Math.abs(a-b) + Math.ulp(a) < Math.nextAfter(0.000001, Double. NEGATIVE_INFINITY)

→ Ссылка