Как правильно заметили другие комментаторы, вы никогда не должны использовать арифметику с плавающей точкой, когда требуются точные значения, например, для денежных значений. Главной причиной действительно является поведение округления, присущее плавающим точкам, но давайте не будем забывать, что работа с плавающими точками означает также необходимость иметь дело с бесконечными значениями и значениями NaN.
В качестве иллюстрации того, что ваш подход просто не работает, приведем простой тестовый код. Я просто добавляю ваш EPSILON
к 10.0
и смотрю, равен ли результат 10.0
- чего не должно быть, поскольку разница явно не на меньше , чем EPSILON
:
double a = 10.0;
double b = 10.0 + EPSILON;
if (!equals(a, b)) {
System.out.println("OK: " + a + " != " + b);
} else {
System.out.println("ERROR: " + a + " == " + b);
}
Сюрприз:
ERROR: 10.0 == 10.00001
Ошибка возникает из-за потери значительных битов при вычитании, если два значения с плавающей запятой имеют разные показатели степени.
Если вы думаете о применении более продвинутого подхода «относительной разности», как предлагали другие комментаторы, вам следует прочитать отличную статью Брюса Доусона Сравнение чисел с плавающей точкой, издание 2012 года , в которой показано, что этот подход имеет аналог недостатки и то, что на самом деле нет отказоустойчивое приближенное сравнение с плавающей точкой, которое работает для всех диапазонов чисел с плавающей точкой.
Для краткости: воздержитесь от double
s для денежных значений и используйте точные числовые представления, такие как BigDecimal
. Ради эффективности вы также можете использовать longs
, интерпретируемый как «миллис» (десятые доли цента), при условии, что вы надежно предотвращаете переполнение и переполнение. Это дает максимально представимые значения 9'223'372'036'854'775.807
, которых должно быть достаточно для большинства реальных приложений.