Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java double comparison epsilon

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.


Yes. Java doubles will hold their precision better than your given epsilon of 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-6 or 0.000001 for a double epsilon in Java with no trouble.

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.


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.


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

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


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

/**
  *@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);
}

As other commenters correctly noted, you should never use 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.

As an illustration that your approach simply doesn't work, here is some simple test code. I simply add your EPSILON to 10.0 and look whether the result is equal to 10.0 -- which it shouldn't be, as the difference is clearly not less than 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 no fail-safe approximate floating-point comparison that works for all ranges of floating-point numbers.

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