繁体   English   中英

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

[英]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#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) ,用于量化之间的差异两个浮点数。

这里涉及两个概念:

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM