54

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?

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;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 3
    You have awaken the wrath of some people here! See here if you really want to use floating point numbers: http://docs.sun.com/source/806-3568/ncg_goldberg.html – Loki Dec 10 '08 at 17:31
  • 5
    Other issues aside, reduce chances of coding error by removing duplicated code. First static method becomes return equals(a,b, EPSILON); – null Apr 27 '13 at 01:32
  • 5
    Speaking just of beauty, `a == b ? true : x` can be replaced by the much nicer, and easier to read version `a == b || x`. – Matthias Apr 02 '14 at 16:13
  • For further discussion of Money and the use of BigDecimal check out: [http://stackoverflow.com/questions/285680/representing-monetary-values-in-java](http://stackoverflow.com/questions/285680/representing-monetary-values-in-java) Thanks [Loki](http://stackoverflow.com/users/39057/loki) for the [interesting read](http://docs.sun.com/source/806-3568/ncg_goldberg.html). I'd never read that and it's great to get a better understanding of this topic. – dshaw Dec 10 '08 at 18:23

9 Answers9

114

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.

diralik
  • 6,391
  • 3
  • 28
  • 52
Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
  • 67
    +1 because indeed you don't ever use floating-point numbers to represent money but -1 (so I didn't modify your count) because using an epsilon is hardly a "stupid hack". It is something fundamental in scientific computing, not a "stupid hack". Goldberg's paper on the subject agrees on that one. – SyntaxT3rr0r Mar 04 '10 at 13:46
  • 57
    Seriously, you shouldn't assume that just because that is how you do things that it is the best way in all cases. Having worked at four different banks, I have never seen a trading system which used BigDecimal, nor would a recommend using them. – Peter Lawrey Oct 21 '10 at 19:35
  • 3
    Peter, what would you recommend for money instead? My preference would be a Long . short based combination for a Money class. However I'm extremely hesitant to roll my own for the situation. I have done it before... but its not something that I can prove that works. – monksy Jan 22 '13 at 04:13
  • 2
    You most probably need to use some "decimal" class to handle actual transfers of money, ESPECIALLY for retail clients. That's "accounting software", but not all "financial software" is "accounting". A trading system deals with A) projected cashflows and their present value, which is an outcome of a model calculation and using BigDecimal to store these numbers makes no sense and B) with actual cashflows which are big enough for people not to worry about missing pennies. Seriously. – quant_dev Apr 30 '13 at 08:23
  • 2
    @PeterLawrey :: The fact that you've never seen people using anything different from floats or double... doesn't immediataly imply that those people know what they were doing. Using floating points to represent money is definitely wrong and can lead to troubles. I've already worked in investment banks and ... yes, I've seen systems in production doing things wrong due to floating point arithmetic. You can either use BigDecimal or fixed point arithmetic instead. Fixed point arithmetic is basically using longs and move the point to the right, say 6 positions, depending on the precision you need. – Richard Gomes Jan 22 '16 at 16:09
  • @RichardGomes I have seen plenty of systems which use `BigDecimal` and `long` and I would say that using `double` is the least error prone as you can easily see a small representation and correct for it but if you have a error with BigDecimal or a `long` it is much harder to spot. – Peter Lawrey Jan 22 '16 at 18:33
  • @PeterLawrey:: The correct way of handling the problem is using fixed point arithmetic, as I've pointed out in another answer to this question. Your argumentation can be classified as "appeal to popularity". https://en.wikipedia.org/wiki/Argumentum_ad_populum – Richard Gomes Jan 24 '16 at 14:19
  • 2
    @RichardGomes suggesting there is one correct answer isn't reflected by reality. You can get representation error regardless of which you chose. The problem with fixed precision is that I would rather be out by 0.000000001 than a factor of 10 or more as a result of a coding error. – Peter Lawrey Jan 24 '16 at 14:27
  • 1
    @PeterLawrey :: I understand your point and my answer is more or less trivial: a good testsuite plus risk checks. In this industry we have to have alarms which tell us when something went very wrong. One example is that Nomura warned Knight Capital when they (Nomura) detected that a client was making too much losses in a row, for 40 minutes, which later was attributed to a programming error. The problem of using doubles is that you will have very small programming errors (rounding errors) very difficult to be detected. – Richard Gomes Feb 24 '16 at 20:15
  • @RichardGomes while true, it is very hard for an error of 1 in 1e15 to build up to a significant one, without producing numbers which look suspect, i.e. easy for a human stay it looks wrong. However, if you use long, or BigDecimal you tend to have code which is harder to read/validate, and you produce numbers which are much harder for a human to say, that looks wrong. – Peter Lawrey Feb 25 '16 at 08:20
  • @Michael Borgwardt : May I suggest you complement your answer mentioning fixed point arithmetic? Both BigDecimal and fixed point arithmetic are recommended solutions for the question; the second one when you have performance constraints. Thanks – Richard Gomes May 11 '17 at 17:57
  • There is no need to insult anybody. – Cupitor Jul 21 '17 at 17:43
17

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.

Alex B
  • 24,678
  • 14
  • 64
  • 87
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.

Community
  • 1
  • 1
Josh Lee
  • 171,072
  • 38
  • 269
  • 275
6

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);
}
carlosvin
  • 979
  • 9
  • 22
6

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

Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
  • 3
    The moredesignpatterns server appears to have gone away and not been replaced. The article is on archive.org, though: http://web.archive.org/web/20090105214308/http://wiki.moredesignpatterns.com/space/Value%2BObject%2BMotivation%2Bv2 – Joshua Goldberg Jun 03 '13 at 19:55
3

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.

Franz D.
  • 1,061
  • 10
  • 23
2

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):

boolean equal(double d1, double d2) {
  double d = d1 / d2;
  return (Math.abs(d - 1.0) < 0.001);
}
Michu93
  • 5,058
  • 7
  • 47
  • 80
Bill
  • 29
  • 1
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.

Karl
  • 8,967
  • 5
  • 29
  • 31
  • Floating point does not represent values like 0.1 accurately since internally it stores the value as 2^exponent * (1 + fraction). Even within reasonable range like 0.001 + 0.001. Run "print int(1.13 * 100.0) / 100.0" if you have perl. It returns 1.12. – Eugene Yokota Dec 10 '08 at 17:45
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)...

Stein G. Strindhaug
  • 5,077
  • 2
  • 28
  • 41