Java双重比较epsilon

时间:2008-12-10 17:11:15

标签: java floating-point currency

我写了一个类来测试相等,小于和大于Java中的两个双重。我的一般情况是比较可以具有半分精度的价格。 59.005比59.395。我选择的epsilon是否适合这些情况?

private final static double EPSILON = 0.00001;


/**
 * Returns true if two doubles are considered equal.  Tests if the absolute
 * difference between two doubles has a difference less then .00001.   This
 * should be fine when comparing prices, because prices have a precision of
 * .001.
 *
 * @param a double to compare.
 * @param b double to compare.
 * @return true true if two doubles are considered equal.
 */
public static boolean equals(double a, double b){
    return a == b ? true : Math.abs(a - b) < EPSILON;
}


/**
 * Returns true if two doubles are considered equal. Tests if the absolute
 * difference between the two doubles has a difference less then a given
 * double (epsilon). Determining the given epsilon is highly dependant on the
 * precision of the doubles that are being compared.
 *
 * @param a double to compare.
 * @param b double to compare
 * @param epsilon double which is compared to the absolute difference of two
 * doubles to determine if they are equal.
 * @return true if a is considered equal to b.
 */
public static boolean equals(double a, double b, double epsilon){
    return a == b ? true : Math.abs(a - b) < epsilon;
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b){
    return greaterThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered greater than the second
 * double.  Test if the difference of first minus second is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered greater than the second
 *              double
 */
public static boolean greaterThan(double a, double b, double epsilon){
    return a - b > epsilon;
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * .00001.  This should be fine when comparing prices, because prices have a
 * precision of .001.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b){
    return lessThan(a, b, EPSILON);
}


/**
 * Returns true if the first double is considered less than the second
 * double.  Test if the difference of second minus first is greater then
 * a given double (epsilon).  Determining the given epsilon is highly
 * dependant on the precision of the doubles that are being compared.
 *
 * @param a first double
 * @param b second double
 * @return true if the first double is considered less than the second
 *              double
 */
public static boolean lessThan(double a, double b, double epsilon){
    return b - a > epsilon;
}

9 个答案:

答案 0 :(得分:101)

你不要用双倍代表金钱。永远不会。请改用java.math.BigDecimal

然后你可以指定如何精确地进行舍入(有时在金融应用程序中由法律规定!)并且不必像这个epsilon那样做愚蠢的黑客。

说真的,使用浮点类型代表金钱是非常不专业的。

答案 1 :(得分:10)

是。 Java双精度将比你给出的0.00001的epsil更好。

由于存储浮点值而发生的任何舍入错误都将小于0.00001。我经常使用1E-6或0.000001在Java中使用双epsilon,没有任何问题。

在相关的说明中,我喜欢epsilon = 1E-5;的格式,因为我觉得它更具可读性(Java中的1E-5 = 1 x 10 ^ -5)。读取代码时,1E-6很容易与1E-5区分,而当看到代码时,0.00001和0.000001看起来非常相似,我认为它们是相同的值。

答案 2 :(得分:8)

哇哇哇哇哇是否有一个特定的原因,你使用浮点货币,或者arbitrary-precision, fixed-point number format会更好吗?我不知道你要解决的具体问题是什么,但你应该考虑半分钱是否真的是你想要使用的东西,或者它只是一个使用不精确数字格式的工件。

答案 3 :(得分:6)

如果你可以使用BigDecimal,那就使用它,否则:

/**
  *@param precision number of decimal digits
  */
public static boolean areEqualDouble(double a, double b, int precision) {
   return Math.abs(a - b) <= Math.pow(10, -precision);
}

答案 4 :(得分:5)

如果您正在处理资金,我建议您查看Money设计模式(最初来自Martin Fowler's book on enterprise architectural design)。

我建议阅读此链接以获取动机: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

答案 5 :(得分:2)

虽然我同意双重对金钱不利的想法,但仍然有兴趣比较双打的想法。特别是epsilon的建议用途仅适用于特定范围内的数字。以下是epsilon的更一般用法,相对于两个数字的比率(省略了0的测试):

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}

答案 6 :(得分:1)

浮点数只有很多有效数字,但它们可以更高。如果您的应用程序将处理大数字,您会注意到epsilon值应该不同。

0.001 + 0.001 = 0.002 但 12,345,678,900,000,000,000,000 + 1 = 12,345,678,900,000,000,000,000 如果你使用浮点和双。这不是一个很好的金钱代表,除非你确定你永远不会在这个系统中处理超过一百万美元。

答案 7 :(得分:1)

仙?如果你计算货币价值,你真的不应该使用浮动值。金钱实际上是可数值。美分或pennys等可以被认为是整数的两个(或任何)最低有效数字。您可以存储并计算货币值作为整数并除以100(例如,在最后两位数之前放置点或逗号两个)。使用float可能导致奇怪的舍入错误......

无论如何,如果你的epsilon应该定义准确度,它看起来有点太小(太准确)......

答案 8 :(得分:0)

正如其他评论者所正确指出的那样,当需要精确值时,永远不会使用浮点运算,例如货币值。主要原因确实是浮点固有的舍入行为,但不要忘记处理浮点意味着还必须处理无限和NaN值。

为了说明您的方法根本不起作用,这里有一些简单的测试代码。我只是将您的EPSILON添加到10.0并查看结果是否等于10.0 - 它不应该是,因为差异显然不是 less < / em>而不是EPSILON

    double a = 10.0;
    double b = 10.0 + EPSILON;
    if (!equals(a, b)) {
        System.out.println("OK: " + a + " != " + b);
    } else {
        System.out.println("ERROR: " + a + " == " + b);
    }

惊喜:

    ERROR: 10.0 == 10.00001

如果两个浮点值具有不同的指数,则如果减去有效位,则会发生错误。

如果您考虑应用更高级的相对差异&#34;根据其他评论者的建议,你应该阅读Bruce Dawson的优秀文章Comparing Floating Point Numbers, 2012 Edition,这表明这种方法有类似的缺点,实际上没有故障安全近似浮动点比较适用于所有浮点数范围。

简而言之:从货币值double开始,并使用BigDecimal之类的确切数字表示。为了提高效率,您还可以使用longs解释为&#34; millis&#34; (十分之一美分),只要您可靠地防止过量和下溢。这会产生9'223'372'036'854'775.807的最大可表示值,这对于大多数实际应用程序来说应该足够了。