Каким должно быть значение эпсилона при двойном эквивалентном сравнении

Вот результат для программы ниже.

value is : 2.7755575615628914E-17
Double.compare with zero : 1
isEqual with zero : true

Мой вопрос: что должно быть эпсилонным значением? Есть ли какой-либо надежный способ получить значение, вместо того, чтобы выбирать номер с неба.


package sandbox;

/**
 *
 * @author yccheok
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        double zero = 1.0/5.0 + 1.0/5.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0;
        System.out.println("value is : " + zero);
        System.out.println("Double.compare with zero : " + Double.compare(zero, 0.0));
        System.out.println("isEqual with zero : " + isEqual(zero, 0.0));
    }

    public static boolean isEqual(double d0, double d1) {
        final double epsilon = 0.0000001;
        return d0 == d1 ? true : Math.abs(d0 - d1) < epsilon;
    }
}

Ответ 1

Ответ на второй вопрос - нет. Величина ошибки конечной машины может быть сколь угодно большой:

public static void main(String[] args) {
    double z = 0.0;
    double x = 0.23;
    double y = 1.0 / x;
    int N = 50000;
    for (int i = 0; i < N; i++) {
        z += x * y - 1.0;
    }
    System.out.println("z should be zero, is " + z);
}

Это дает ~5.55E-12, но если вы увеличиваете N, вы можете получить практически любой желаемый уровень ошибки.

Существует огромное количество прошлых и текущих исследований о том, как писать численно устойчивые алгоритмы. Это трудная проблема.

Ответ 2

Мне нравится (псевдокод, я не делаю java)

bool fuzzyEquals(double a, double b)
{
    return abs(a - b) < eps * max(abs(a), abs(b));
}

с epsilon, являющимся в несколько раз машинным эпсилон. Возьмите 10 ^ -12, если вы не знаете, что использовать.

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

Точной точкой является использование относительной точности, а не абсолютной.

Ответ 3

Нет ни одного правильного значения. Вы должны вычислить его относительно величины соответствующих чисел. То, что вы в основном имеете дело, - это ряд значительных цифр, а не определенная величина. Если, например, ваши номера находятся в диапазоне 1e-100, и ваши расчеты должны содержать примерно 8 значащих цифр, тогда ваш эпсилон должен быть около 1e-108. Если бы вы делали одни и те же вычисления на числах в диапазоне 1e + 200, то ваш эпсилон составлял бы около 1e + 192 (т.е. Epsilon ~ = величина - значащие цифры).

Я также хотел бы отметить, что isEqual - плохое имя - вам нужно что-то вроде isNearlyEQual. По одной причине люди вполне разумно ожидают "равного" транзитивности. По крайней мере, вам нужно передать идею о том, что результат уже не является транзитивным, т.е. С вашим определением isEqual, isEqual(a, c) может быть ложным, даже если isEqual(a, b) и isEqual(b, c) являются истинными.

Изменить: (в ответ на комментарии): Я сказал: "Если [...] ваши расчеты должны содержать примерно 8 значащих цифр, тогда ваш эпсилон должен быть...". В основном, речь идет о том, какие расчеты вы делаете и насколько точно вы можете потерять в процессе, чтобы дать разумное предположение о том, насколько велика разница, прежде чем она станет значимой. Не зная, что вы делаете, я не могу этого догадаться.

Что касается величины epsilon: нет, для него не имеет смысла всегда быть меньше или равно 1. Число с плавающей запятой может поддерживать только ограниченную точность. В случае с плавающей точкой двойной точности IEEE максимальная точность, которая может быть представлена, составляет около 20 десятичных цифр. Это означает, что если вы начинаете с 1e + 200, абсолютная наименьшая разница от того числа, которое машина может представлять вообще, составляет около 1e + 180 (а double может представлять числа до ~ 1e + 308, в этот момент самая маленькая разница, может быть представлено ~ 1e + 288).

Ответ 4

В isEqual, есть что-то вроде:

epsilon = Math.max(Math.ulp(d0), Math.ulp(d1))

ulp двойного значения - это положительное расстояние между этим значение с плавающей запятой и двойное значение, большее по величине. [1]

[1] http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#ulp%28double%29

Ответ 5

Вы должны сначала прочитать https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/.

В нем обсуждаются различные способы сравнения чисел с плавающей точкой: абсолютный допуск, относительная допуск, расстояние ulp. Это довольно хороший аргумент в том, что проверка ulp - это путь. Случай зависает вокруг аргумента: если вы хотите проверить, совпадают ли два числа с плавающей запятой, вы должны учитывать расстояние между представляемыми поплавками. Другими словами, вы должны проверить, находятся ли два числа внутри e-поплавков друг друга.

Алгоритмы задаются в C, но могут быть переведены в java с помощью java.lang.Double#doubleToLongBits и java.lang.Float#floatToIntBits для реализации кастинга с плавающих на целые типы. Кроме того, с помощью java > 1.5 существуют методы ulp(double) ulp(float) и для java > 1.6 nextUp(double) nextUp(float) nextAfter(double, double) nextAfter(float, float), которые полезны для количественной оценки разницы между двумя числами с плавающей запятой.

Ответ 6

Здесь есть два понятия:

  • Единица точности машины: Double.ulp()
  • Точность машины для заданного double d: Double.ulp(d)

Если вы вызываете Double.ulp(), вы получите модуль точности машины, который является точной точностью, которую вы можете ожидать от определенной аппаратной платформы... каким бы это ни было определение!

Если вы вызываете Double.ulp(d), вы получите точность машины для double d. Другими словами, каждая double d имеет свою специфическую точность. Это более полезно, чем предыдущий абзац.

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

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

В случае с финансами, например, никогда не используйте арифметику с плавающей запятой. Никогда не используйте doubles или floats, когда вы имеете дело с деньгами. Никогда. Период. Вы можете использовать BigDecimal или арифметику с фиксированной точкой, в зависимости от обстоятельств.

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

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