有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

浮点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;
}

共 (6) 个答案

  1. # 1 楼答案

    如果可以使用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);
    }
    
  2. # 2 楼答案

    哇,哇,哇。你使用浮点数作为货币是有具体原因的,还是使用arbitrary-precision, fixed-point number format会更好?我不知道你要解决的具体问题是什么,但你应该想想半美分是否真的是你想要解决的问题,或者它是否只是使用不精确数字格式的产物

  3. # 3 楼答案

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

    下面是一些简单的测试代码,说明您的方法根本不起作用。我只是简单地把你的EPSILON加到10.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);
        }
    

    惊喜:

        ERROR: 10.0 == 10.00001
    

    如果两个浮点值具有不同的指数,则减法中的有效位丢失会导致错误

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

    简而言之:不要用double来表示货币价值,使用精确的数字表示法,比如BigDecimal。为了提高效率,您也可以使用longs解释为“毫”(十分之一美分),只要您能够可靠地防止过流和下流。这将产生9'223'372'036'854'775.807的最大可表示值,这对于大多数实际应用来说应该足够了

  4. # 5 楼答案

    你不能用double来表示金钱。永远不会。改用^{}

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

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

  5. # 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看起来非常相似,我认为它们是相同的值