53

我写了一个类,用 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;
}
4

9 回答 9

112

You do NOT use double to represent money. Not ever. Use java.math.BigDecimal instead.

Then you can specify how exactly to do rounding (which is sometimes dictated by law in financial applications!) and don't have to do stupid hacks like this epsilon thing.

Seriously, using floating point types to represent money is extremely unprofessional.

于 2008-12-10T17:24:11.250 回答
16

是的。Java 双精度将比您给定的 epsilon 0.00001 更好地保持其精度。

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

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

于 2008-12-10T17:15:12.797 回答
11

Whoa whoa whoa. Is there a specific reason you're using floating-point for currency, or would things be better off with an arbitrary-precision, fixed-point number format? I have no idea what the specific problem that you're trying to solve is, but you should think about whether or not half a cent is really something you want to work with, or if it's just an artifact of using an imprecise number format.

于 2008-12-10T17:21:13.517 回答
6

如果您正在处理金钱,我建议检查金钱设计模式(最初来自Martin Fowler 的企业架构设计书)。

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

于 2008-12-10T17:30:29.053 回答
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);
}
于 2014-02-04T17:06:49.193 回答
3

正如其他评论者正确指出的那样,当需要精确值时,您不应该使用浮点运算,例如货币值。主要原因确实是浮点固有的舍入行为,但我们不要忘记处理浮点也意味着必须处理无穷大和 NaN 值。

为了说明您的方法根本行不通,这里有一些简单的测试代码。我只是将您添加EPSILON10.0并查看结果是否等于10.0- 它不应该是,因为差异显然不小于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

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

如果您想像其他评论者建议的那样应用更高级的“相对差异”方法,您应该阅读 Bruce Dawson 的优秀文章Comparing Floating Point Numbers, 2012 Edition,它表明这种方法具有类似的缺点并且实际上没有失败-适用于所有浮点数范围的安全近似浮点比较。

简而言之:double对于货币价值,不要使用 s,并使用精确的数字表示,例如BigDecimal. 为了提高效率,您也可以使用longs解释为“毫”(十分之一美分),只要您可靠地防止上溢和下溢。这会产生 的最大可表示值9'223'372'036'854'775.807,这对于大多数实际应用程序来说应该足够了。

于 2015-02-26T17:42:43.643 回答
2

虽然我同意双倍不利于金钱的观点,但比较双倍的想法仍然很有趣。特别是 epsilon 的建议使用仅适用于特定范围内的数字。这是一个更普遍的 epsilon 用法,相对于两个数字的比率(省略 0 的测试):

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}
于 2010-10-21T19:09:10.103 回答
1

Floating point numbers only have so many significant digits, but they can go much higher. If your app will ever handle large numbers, you will notice the epsilon value should be different.

0.001+0.001 = 0.002 BUT 12,345,678,900,000,000,000,000+1=12,345,678,900,000,000,000,000 if you are using floating point and double. It's not a good representation of money, unless you are damn sure you'll never handle more than a million dollars in this system.

于 2008-12-10T17:24:55.930 回答
1

Cents? If you're calculationg money values you really shouldn't use float values. Money is actually countable values. The cents or pennys etc. could be considered the two (or whatever) least significant digits of an integer. You could store, and calculate money values as integers and divide by 100 (e.g. place dot or comma two before the two last digits). Using float's can lead to strange rounding errors...

Anyway, if your epsilon is supposed to define the accuracy, it looks a bit too small (too accurate)...

于 2008-12-10T17:27:41.600 回答