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