При сравнении двух вещественных чисел получаем разный результат
Сравнить два вещественных числа между собой возможно лишь с некоторой степенью точности. Если два числа отличаются на малое значение, то можно считать их равными. Необходимую степень точности мы можем задать сами.
Нужно сравнить два числа и проверить равны ли они с точностью до одной миллионной (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 шт):
Ноги растут всё из того же 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
Как в том анекдоте про академика Струве в Пулково, за "вычислитель должен быть девиц". Построим неформальную модель.
Что такое 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)