执行双值相等比较时,epsilon值应该是多少

这是以下程序的输出。

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; } } 

你的第二个问题的答案是否定的。 有限机器精度误差的大小可以任意大:

 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你可以得到你想要的任何级别的错误。

关于如何编写数值稳定的算法,有大量过去和现在的研究。 这是一个难题。

我喜欢(伪代码,我不做java)

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

与epsilon是机器epsilon的几倍。 如果您不知道该使用什么,请取10 ^ -12。

然而,这非常依赖于问题。 如果给出a和b的计算容易出现舍入误差,或者涉及许多操作,或者它们本身处于某种(已知的)准确度之内,那么你想要采用更大的epsilon。

重点是使用相对精度,而不是绝对精度。

没有一个正确的价值。 您需要相对于所涉及的数字的大小来计算它。 你基本上处理的是一些有效数字,而不是具体的数字。 例如,如果您的数字都在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)。

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

这里涉及两个概念:

  1. 机器精度单位: Double.ulp()
  2. 给定double d机器精度double dDouble.ulp(d)

如果你调用Double.ulp()你将获得机器精度单位 ,这是你可以从某个硬件平台获得的精度…无论这个定义是什么!

如果你调用Double.ulp(d) ,你将获得double d的机器精度。 换句话说,每个double d都有其特定的精度。 这比前一段更有用。

当您执行涉及级联计算的迭代时,您必须特别注意细节,即:当前计算中使用先前计算的结果时。 这是因为错误在这些情况下累积,并且可能在某些情况下最终导致结果偏离它们应该提供的真实价值。 在某些情况下,累积误差的大小甚至可能大于真实值。 在这里看一些灾难性的例子 。

在某些业务领域,数值计算错误根本不可接受。 根据业务领域,其规则,要求和特性,您必须采用其他方法来简化选择使用浮点运算(即: doublesfloats )。

以财务为例, 永远不要使用浮点运算 。 当你处理钱财时,永远不要使用doublesfloats 。 决不。 期。 您可以根据具体情况使用BigDecimal或定点算法 。

在处理股票价格的具体情况下,您知道价格总是精确到5位数,在这种情况下, 定点运算足够多,并且还提供了您可能获得的最大性能,这是一个非常强大和常见的要求在此业务领域。

如果业务领域确实需要数值计算,那么在这种情况下,您必须确保在严格且非常小心的控制下保持错误传播。 这是一个很长的主题,有很多技术,很多时候开发人员忽略了这个问题,只是认为对一个方法有一个神奇的调用,它可以为他们完成所有艰苦的工作。 不,它没有。 你必须做你的研究,做你的功课,并做所有必要的努力工作,以确保你控制错误。 您需要准确了解您实施的数值算法的具体情况。

你一定要先阅读https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 。

它讨论了比较浮点数的各种方法:绝对容差,相对容差,ulp距离。 它提供了一个相当好的论据,即ulp检查是要走的路。 案例取决于如果要检查两个浮点数是否相同的参数,则必须考虑可表示浮点数之间的距离。 换句话说,您应该检查这两个数字是否在彼此的e浮点数内。

算法在C中给出,但可以使用java.lang.Double#doubleToLongBitsjava.lang.Float#floatToIntBits转换为java,以实现从浮动到整数类型的转换。 此外,使用java> 1.5时,有方法ulp(double) ulp(float)和java> 1.6 nextUp(double) nextUp(float) nextAfter(double, double) nextAfter(float, float) ,用于量化之间的差异两个浮点数。