浮点Java双比较ε
我编写了一个类,用Java中的两个double测试等式、小于和大于。我的一般情况是比较价格,精确到半美分。59.005与59.395相比。我选择的ε是否适合这些情况
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 楼答案
如果可以使用BigDecimal,则使用它,否则:
# 2 楼答案
哇,哇,哇。你使用浮点数作为货币是有具体原因的,还是使用arbitrary-precision, fixed-point number format会更好?我不知道你要解决的具体问题是什么,但你应该想想半美分是否真的是你想要解决的问题,或者它是否只是使用不精确数字格式的产物
# 3 楼答案
正如其他评论者正确指出的那样,当需要精确的值时,如货币值,你应该永远不要使用浮点运算。主要原因确实是浮点固有的舍入行为,但我们不要忘记,处理浮点意味着还必须处理无穷大和NaN值
下面是一些简单的测试代码,说明您的方法根本不起作用。我只是简单地把你的
EPSILON
加到10.0
中,看看结果是否等于10.0
——这不应该是这样,因为差别显然不小于EPSILON
:惊喜:
如果两个浮点值具有不同的指数,则减法中的有效位丢失会导致错误
如果你想采用其他评论者建议的更高级的“相对差异”方法,你应该阅读Bruce Dawson的优秀文章Comparing Floating Point Numbers, 2012 Edition,这表明这种方法也有类似的缺点,而且实际上有no故障安全近似浮点比较,适用于所有范围的浮点数
简而言之:不要用
double
来表示货币价值,使用精确的数字表示法,比如BigDecimal
。为了提高效率,您也可以使用longs
解释为“毫”(十分之一美分),只要您能够可靠地防止过流和下流。这将产生9'223'372'036'854'775.807
的最大可表示值,这对于大多数实际应用来说应该足够了# 4 楼答案
如果你在处理钱的问题,我建议你检查一下钱的设计模式(最初来自Martin Fowler's book on enterprise architectural design)
我建议阅读此链接以了解动机: http://wiki.moredesignpatterns.com/space/Value+Object+Motivation+v2
# 5 楼答案
你不能用double来表示金钱。永远不会。改用^{}
然后,您可以指定如何精确地进行舍入(在金融应用程序中,这有时是由法律规定的!)不必做像epsilon这样愚蠢的黑客
说真的,使用浮点类型来表示货币是非常不专业的
# 6 楼答案
对。Java Double将比给定的epsilon 0.00001更精确
由于存储浮点值而产生的任何舍入误差将小于0.00001。在Java中,我经常使用
1E-6
或0.000001表示双ε,没有任何问题另一方面,我喜欢
epsilon = 1E-5;
的格式,因为我觉得它更可读(Java中的1E-5=1 x 10^-5)。当读取代码时,1E-6很容易与1E-5区分,而当浏览代码时,0.00001和0.000001看起来非常相似,我认为它们是相同的值