Java双重比较epsilon

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/356807/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 13:47:32  来源:igfitidea点击:

Java double comparison epsilon

javafloating-pointcurrency

提问by

I wrote a class that tests for equality, less than, and greater than with two doubles in Java. My general case is comparing price that can have an accuracy of a half cent. 59.005 compared to 59.395. Is the epsilon I chose adequate for those cases?

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

回答by Alex B

Yes. Java doubles will hold their precision better than your given epsilon of 0.00001.

是的。Java doubles 的精度将比您给定的 epsilon 0.00001 更好。

Any rounding error that occurs due to the storage of floating point values will occur smaller than 0.00001. I regularly use 1E-6or 0.000001 for a double epsilon in Java with no trouble.

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

On a related note, I like the format of epsilon = 1E-5;because I feel it is more readable (1E-5 in Java = 1 x 10^-5). 1E-6 is easy to distinguish from 1E-5 when reading code whereas 0.00001 and 0.000001 look so similar when glancing at code I think they are the same value.

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

回答by Josh Lee

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.

哇哇哇。您使用浮点作为货币是否有特定原因,或者使用任意精度的定点数字格式会更好吗?我不知道您要解决的具体问题是什么,但您应该考虑半分钱是否真的是您想要使用的东西,或者它是否只是使用不精确数字格式的产物。

回答by Michael Borgwardt

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

您不使用 double 来表示金钱。永远不会。使用java.math.BigDecimal来代替。

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.

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

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

说真的,使用浮点类型来表示货币是非常不专业的。

回答by Karl

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.

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

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.

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

回答by Stein G. Strindhaug

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...

美分?如果您正在计算货币价值,您真的不应该使用浮点价值。金钱实际上是可数的值。美分或便士等可以被视为整数的两个(或其他)最低有效数字。您可以将货币值存储和计算为整数,然后除以 100(例如,在最后两位数字之前放置两个点或逗号)。使用浮点数会导致奇怪的舍入错误......

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

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

回答by Yuval Adam

If you are dealing with money I suggest checking the Money design pattern (originally from Martin Fowler's book on enterprise architectural design).

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

I suggest reading this link for the motivation: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

我建议阅读这个链接的动机:http: //wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2

回答by Bill

While I agree with the idea that double is bad for money, still the idea of comparing doubles has interest. In particular the suggested use of epsilon is only suited to numbers in a specific range. Here is a more general use of an epsilon, relative to the ratio of the two numbers (test for 0 is omitted):

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

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

回答by carlosvin

If you can use BigDecimal, then use it, else:

如果您可以使用 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);
}

回答by Franz D.

As other commenters correctly noted, you should neveruse floating-point arithmetic when exact values are required, such as for monetary values. The main reason is indeed the rounding behaviour inherent in floating-points, but let's not forget that dealing with floating-points means also having to deal with infinite and NaN values.

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

As an illustration that your approach simply doesn't work, here is some simple test code. I simply add your EPSILONto 10.0and look whether the result is equal to 10.0-- which it shouldn't be, as the difference is clearly not lessthan EPSILON:

为了说明您的方法根本不起作用,这里有一些简单的测试代码。我只是添加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);
    }

Surprise:

惊喜:

    ERROR: 10.0 == 10.00001

The errors occurs because of the loss if significant bits on subtraction if two floating-point values have different exponents.

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

If you think of applying a more advanced "relative difference" approach as suggested by other commenters, you should read Bruce Dawson's excellent article Comparing Floating Point Numbers, 2012 Edition, which shows that this approach has similar shortcomings and that there is actually nofail-safe approximate floating-point comparison that works for all ranges of floating-point numbers.

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

To make things short: Abstain from doubles for monetary values, and use exact number representations such as BigDecimal. For the sake of efficiency, you could also use longsinterpreted as "millis" (tenths of cents), as long as you reliably prevent over- and underflows. This yields a maximum representable values of 9'223'372'036'854'775.807, which should be enough for most real-world applications.

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