views:

5427

answers:

10

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

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;
}
}
+1  A: 

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
+1  A: 

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.

jleedev
+26  A: 

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.

Michael Borgwardt
+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.
Webinator
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
+1  A: 

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
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.
eed3si9n
A: 

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

I definately agree with brazzy: always use BigDecimal for money.

Markus
+4  A: 

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

yes i totally agree i don't want to be using double, but this is a legacy system and it is not trivial to change every thing to long or BigDecimal. So i'm trying to provide some consistency. hopefully i'll eventually be able to remove it.

thanks much

Please tell me you're not working for a Financial Services company!
Ken Gentle
You can have your internal classes translate to BigDecimal and back to double when output is needed to the legacy system!
Karl
A: 

For further discussion of Money and the use of BigDecimal check out: http://stackoverflow.com/questions/285680/representing-monetary-values-in-java

Thanks Loki for the interesting read. I'd never read that and it's great to get a better understanding of this topic.

dshaw
A: 

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's a more general use of an epsilon, relative to the ratio of the two numbers (test for 0 omitted):

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

Bill