Java двойное сравнение эпсилон

Я написал класс, который проверяет на равенство, меньше и больше, чем с двумя двойными в Java. Мой общий случай - сравнение цены, которая может иметь точность в полцента. 59,005 против 59,395. Подходит ли эпсилон, который я выбрал, для этих случаев?

private final static double EPSILON = 0.00001;


/**
 * Returns true if two doubles are considered equal.  Tests if the absolute
 * difference between two doubles has a difference less then .00001.   This
 * should be fine when comparing prices, because prices have a precision of
 * .001.
 *
 * @param a double to compare.
 * @param b double to compare.
 * @return true true if two doubles are considered equal.
 */
public static boolean equals(double a, double b){
    return a == b ? true : Math.abs(a - b) < EPSILON;
}


/**
 * Returns true if two doubles are considered equal. Tests if the absolute
 * difference between the two doubles has a difference less then a given
 * double (epsilon). Determining the given epsilon is highly dependant on the
 * precision of the doubles that are being compared.
 *
 * @param a double to compare.
 * @param b double to compare
 * @param epsilon double which is compared to the absolute difference of two
 * doubles to determine if they are equal.
 * @return true if a is considered equal to b.
 */
public static boolean equals(double a, double b, double epsilon){
    return a == b ? true : Math.abs(a - b) < epsilon;
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b){
    return greaterThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b, double epsilon){
    return a - b > epsilon;
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b){
    return lessThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b, double epsilon){
    return b - a > epsilon;
}
10.12.2008 17:11:15
Вы пробудили гнев некоторых людей здесь! Смотрите здесь, если вы действительно хотите использовать числа с плавающей запятой: docs.sun.com/source/806-3568/ncg_goldberg.html
Loki 10.12.2008 17:31:52
Для дальнейшего обсуждения Money и использования BigDecimal проверьте: http://stackoverflow.com/questions/285680/representing-monetary-values-in-java Спасибо Loki за интересное чтение . Я никогда не читал это, и это здорово, чтобы лучше понять эту тему.
dshaw 10.12.2008 18:23:46
Помимо других проблем, уменьшите вероятность ошибки кодирования, удалив дублирующийся код. Первый статический метод становится равным return (a, b, EPSILON);
null 27.04.2013 01:32:15
Говоря просто о красоте, a == b ? true : xего можно заменить на более приятную и удобную для чтения версию a == b || x.
Matthias 2.04.2014 16:13:56
9 ОТВЕТОВ

Да. Двойники Java будут держать их точность лучше, чем ваш заданный эпсилон 0,00001.

Любая ошибка округления, возникающая из-за хранения значений с плавающей запятой, будет меньше 0,00001. Я регулярно использую 1E-6или 0,000001 для двойного эпсилона в Java без проблем.

Что касается примечания, мне нравится формат, epsilon = 1E-5;потому что я чувствую, что он более читабелен (1E-5 в Java = 1 x 10 ^ -5). 1E-6 легко отличить от 1E-5 при чтении кода, тогда как 0,00001 и 0,000001 выглядят очень похожими при взгляде на код. Я думаю, что они имеют одинаковое значение.

14
10.12.2008 17:15:12

Вау Вау Вау. Есть ли конкретная причина, по которой вы используете плавающую точку для валюты, или было бы лучше с произвольным форматом чисел с фиксированной точкой ? Я понятия не имею, в чем заключается конкретная проблема, которую вы пытаетесь решить, но вам следует подумать о том, действительно ли пол-цента - это то, с чем вы хотите работать, или это просто артефакт использования неточного числового формата.

11
23.05.2017 12:26:06

Вы НЕ используете double для представления денег. Никогда не. Используйте java.math.BigDecimalвместо этого.

Затем вы можете указать, как именно делать округления (что иногда диктуется законом в финансовых приложениях!), И вам не нужно делать глупые хаки, подобные этой эпсилон-штуке.

Серьезно, использование типов с плавающей запятой для представления денег крайне непрофессионально.

106
26.11.2017 11:59:20
+1, потому что на самом деле вы никогда не используете числа с плавающей точкой для обозначения денег, а -1 (поэтому я не изменил ваш счет), потому что использование эпсилона вряд ли является «глупым хаком». Это нечто фундаментальное в научных вычислениях, а не «глупый взлом». Статья Голдберга на эту тему соглашается с этим.
SyntaxT3rr0r 4.03.2010 13:46:38
Серьезно, вы не должны предполагать это только потому, что именно так вы делаете вещи, и это лучший способ во всех случаях. Работая в четырех разных банках, я никогда не видел торговой системы, которая бы использовала BigDecimal, и не рекомендовал бы использовать их.
Peter Lawrey 21.10.2010 19:35:32
Питер, что бы ты посоветовал вместо денег? Мое предпочтение было бы длинным. короткая комбинация для класса Money. Тем не менее, я крайне не решаюсь откатиться для ситуации. Я делал это раньше ... но это не то, что я могу доказать, что работает.
monksy 22.01.2013 04:13:24
Скорее всего, вам нужно использовать какой-то «десятичный» класс для обработки фактических переводов денег, ОСОБЕННО для розничных клиентов. Это «бухгалтерское программное обеспечение», но не все «финансовое программное обеспечение» является «бухгалтерским». Торговая система имеет дело с A) прогнозируемыми денежными потоками и их текущей стоимостью, которая является результатом модельного расчета, и использование BigDecimal для хранения этих чисел не имеет смысла, и B) с фактическими денежными потоками, которые достаточно велики, чтобы люди не беспокоились о пропущенных копейках. , Шутки в сторону.
quant_dev 30.04.2013 08:23:13
@RichardGomes, предполагающие, что есть один правильный ответ, не отражены действительностью. Вы можете получить ошибку представления независимо от того, что вы выбрали. Проблема с фиксированной точностью заключается в том, что я предпочел бы выйти на 0,000000001, а не в 10 или более раз из-за ошибки кодирования.
Peter Lawrey 24.01.2016 14:27:40

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

0,001 + 0,001 = 0,002 НО 12 345 678 900 000 000 000 000 + 1 = 12 345 678 900 000 000 000 000, если вы используете число с плавающей запятой и двойное число. Это не очень хорошее представление о деньгах, если только вы не уверены, что никогда не справитесь с более чем миллионом долларов в этой системе.

1
10.12.2008 17:24:55
Плавающая точка не представляет значения, подобные 0,1, точно, поскольку внутренне она хранит значение как 2 ^ экспонента * (1 + дробь). Даже в разумных пределах, таких как 0,001 + 0,001. Запустите «print int (1.13 * 100.0) / 100.0», если у вас есть Perl. Возвращает 1.12.
Eugene Yokota 10.12.2008 17:45:28

Cents? Если вы рассчитываете денежные значения, вам не следует использовать значения с плавающей запятой. Деньги на самом деле исчисляются ценностями. Центы или пенни и т. Д. Можно считать двумя (или любыми) младшими цифрами целого числа. Вы можете хранить и вычислять денежные значения как целые числа и делить на 100 (например, ставьте точку или запятую два перед двумя последними цифрами). Использование float может привести к странным ошибкам округления ...

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

1
10.12.2008 17:27:41

Если вы имеете дело с деньгами, я предлагаю проверить шаблон дизайна денег (первоначально из книги Мартина Фаулера по архитектуре предприятия ).

Я предлагаю прочитать эту ссылку для мотивации: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

5
10.12.2008 18:48:16
Сервер moredesignpatterns, кажется, ушел и не был заменен. Статья на сайте archive.org, однако: web.archive.org/web/20090105214308/http://…
Joshua Goldberg 3.06.2013 19:55:47

Хотя я согласен с идеей, что двойное плохо для денег, все же идея сравнения двойных имеет интерес. В частности, предлагаемое использование эпсилона подходит только для чисел в определенном диапазоне. Вот более общее использование эпсилона относительно соотношения двух чисел (тест на 0 опущен):

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}
2
22.04.2018 16:33:58
Это очень опасно из-за нулевого деления.
lethalman 18.12.2011 10:18:08
Действительно, 0.000001и 0не было бы равных с этим кодом.
Joey 9.07.2012 08:02:07

Если вы можете использовать BigDecimal, то используйте его, иначе:

/**
  *@param precision number of decimal digits
  */
public static boolean areEqualDouble(double a, double b, int precision) {
   return Math.abs(a - b) <= Math.pow(10, -precision);
}
6
4.02.2014 17:06:49
Разве это не должно быть Double.compare (Math.abs (ab), Math.pow (10, -precision))?
Michael 18.07.2014 20:19:55

Как правильно заметили другие комментаторы, вы никогда не должны использовать арифметику с плавающей запятой, когда требуются точные значения, например, для денежных значений. Главной причиной является поведение округления, присущее плавающим точкам, но давайте не будем забывать, что работа с плавающими точками означает также необходимость иметь дело с бесконечными значениями и значениями 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 издание , которое показывает , что такой подход имеет аналогичные недостатки , и что на самом деле нет fail- безопасное приближенное сравнение с плавающей точкой, которое работает для всех диапазонов чисел с плавающей точкой.

Короче говоря: воздержитесь от doubles для денежных значений и используйте точные числовые представления, такие как BigDecimal. Ради эффективности вы также можете использовать longsинтерпретированный как «миллис» (десятые доли цента), если вы надежно предотвращаете переполнение и переполнение. Это дает максимально представимые значения 9'223'372'036'854'775.807, которых должно быть достаточно для большинства реальных приложений.

1
26.02.2015 17:42:43