[英]What should be the epsilon value when performing double value equal comparison
这是以下程序的输出。
value is : 2.7755575615628914E-17
Double.compare with zero : 1
isEqual with zero : true
我的问题是,什么应该是epsilon值? 是否有任何有力的方法来获得价值,而不是从天空中挑选一个数字。
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;
}
}
我喜欢(伪代码,我不做java)
bool fuzzyEquals(double a, double b)
{
return abs(a - b) < eps * max(abs(a), abs(b));
}
与epsilon是机器epsilon的几倍。 如果您不知道该使用什么,请取10 ^ -12。
然而,这非常依赖于问题。 如果给出a和b的计算容易出现舍入误差,或者涉及许多操作,或者它们本身处于某种(已知的)准确度之内,那么你需要采用更大的epsilon。
重点是使用相对精度,而不是绝对精度。
在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
没有一个正确的价值。 您需要相对于所涉及的数字的大小来计算它。 你基本上处理的是一些有效数字,而不是具体的数字。 例如,如果您的数字都在1e-100范围内,并且您的计算应保持大致8位有效数字,那么您的epsilon应该在1e-108左右。 如果你对1e + 200范围内的数字做了相同的计算,那么你的epsilon将在1e + 192左右(即epsilon~ = magnitude - 有效数字)。
我还注意到isEqual
是一个糟糕的名字 - 你想要像isNearlyEQual
这样的isNearlyEQual
。 出于一个原因,人们相当合理地期望“平等”是可传递的。 至少,您需要传达结果不再具有传递性的想法 - 即,使用isEqual
的定义, isEqual(a, c)
可以为false,即使isEqual(a, b)
和isEqual(b, c)
都是真的。
编辑:(回应评论):我说“如果[...]你的计算应保持大致8位有效数字,那么你的epsilon应该......”。 基本上,它涉及到你正在做什么计算,以及你在这个过程中可能失去多少精确度,以提供一个合理的猜测,在重要之前差异有多大。 在不知道你正在做的计算的情况下,我无法猜测。
就epsilon的大小而言:不,它总是小于或等于1是没有意义的。浮点数只能保持有限的精度。 在IEEE双精度浮点的情况下,可以表示的最大精度是大约20个十进制数字。 这意味着如果开始时1E + 200,从该数字机器可在所有代表的绝对最小的差为约1E + 180(和双可以表示数字高达〜1E + 308,在该点的最小差可以表示为~1e + 288)。
你的第二个问题的答案是否定的。 有限机器精度误差的大小可以任意大:
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
你可以得到你想要的任何级别的错误。
关于如何编写数值稳定的算法,有大量过去和现在的研究。 这是一个难题。
你一定要先阅读https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 。
它讨论了比较浮点数的各种方法:绝对容差,相对容差,ulp距离。 它提供了一个相当不错的论据,即ulp检查是要走的路。 这个案例取决于如果你想检查两个浮点数是否相同,你必须考虑可表示的浮点数之间的距离。 换句话说,你应该检查这两个数字是否在彼此的e浮点数内。
算法在C中给出,但可以使用java.lang.Double#doubleToLongBits
和java.lang.Float#floatToIntBits
转换为java,以实现从浮动到整数类型的转换。 此外,使用java> 1.5时,有方法ulp(double)
ulp(float)
和java> 1.6 nextUp(double)
nextUp(float)
nextAfter(double, double)
nextAfter(float, float)
,用于量化之间的差异两个浮点数。
这里涉及两个概念:
Double.ulp()
double d
机器精度double d
: Double.ulp(d)
如果你调用Double.ulp()
你将获得机器精度单位 ,这是你可以从某个硬件平台获得的精度...无论这个定义是什么!
如果你调用Double.ulp(d)
,你将获得double d
的机器精度。 换句话说,每个double d
都有其特定的精度。 这比前一段更有用。
当您执行涉及级联计算的迭代时,您必须特别注意细节,即:当前计算中使用先前计算的结果时。 这是因为错误在这些情况下累积,并且在某些情况下可能最终导致结果偏离它们应该提供的真实价值。 在某些情况下,累积误差的大小甚至可能大于真实值。 在这里看一些灾难性的例子 。
在某些业务领域,数值计算错误根本不可接受。 根据业务领域,其规则,要求和特性,您必须采用其他方法来简化选择使用浮点运算(即: doubles
或floats
)。
以财务为例, 永远不要使用浮点运算 。 当你处理钱财时,永远不要使用doubles
或floats
。 决不。 期。 您可以根据具体情况使用BigDecimal或定点算法 。
在处理股票价格的具体情况下,您知道价格总是精确到5位数,在这种情况下, 定点算术足够多,并且还提供了您可能获得的最大性能,这是一个非常强大和普遍的要求在此业务领域。
如果业务领域确实需要数值计算,那么在这种情况下,您必须确保在严格且非常小心的控制下保持错误传播。 这是一个很长的主题,有很多技术,很多时候开发人员忽略了这个问题,只是认为对一个为他们做了所有艰苦工作的方法有一个神奇的调用。 不,它没有。 你必须做你的研究,做你的功课,并做所有必要的努力工作,以确保你控制错误。 您需要准确了解您实施的数值算法的具体情况。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.