How Java compares floating point numbers correctly

Look at the following code to compare D1 and D2 floating-point numbers. What will be the output result?

double d1 = .1 * 3;
double d2 = .3;
System.out.println(d1 == d2);

According to normal logic, the result of D1 after calculation should be 0.3, and the last printed result should be true, right? But when you run it, you will find that the result is not true but false.

Output D1 and find that the answer is not the imaginary 0.3 but 0.300000000000000 4, so the comparison result with D2 is naturally false

How to correctly compare floating-point numbers (single precision float and double precision) is not only a Java specific problem, but also a precision problem when the IEEE 754 standard is used to store floating-point numbers in the computer memory.

In the process of storage and conversion, floating-point numbers are easy to cause some small rounding errors. It is for this reason that "= =" operator cannot be used when comparing floating-point numbers - it is required to be exactly equal in the strict sense.

So how to correctly compare floating point numbers? There are two options.

The first scheme is to allow a little error between the two values (specify a threshold), and use the math. ABS () method to calculate the absolute value of the difference between the two floating-point numbers. If the difference is within the threshold range, we think that the two floating-point numbers are equal.

final double THRESHOLD = .0001;

double d1 = .1 * 3;
double d2 = .3;

if(Math.abs(d1-d2) < THRESHOLD) {
	System.out.println("d1 和 d2 相等");
} else {
	System.out.println("d1 和 d2 不相等");
}

Math. The ABS () method is used to return the absolute value of double. If double is less than 0, it returns the positive value of double. Otherwise, it returns double. In other words, the result after ABS () is absolutely greater than 0. If the result is less than the threshold, we think D1 and D2 are equal.

The second solution is to use the BigDecimal class, which can specify the mode and precision to be rounded, so that the rounding error can be solved.

Use the CompareTo () method of BigDecimal class to compare two numbers. This method will ignore the number of digits after the decimal point. How to understand this sentence? For example, the digits of 2.0 and 2.00 are different, but their values are equal.

a. CompareTo (b) returns 0 if a and B are equal, otherwise - 1.

BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");

System.out.println(a.equals(b));
System.out.println(a.compareTo(b) == 0);

In the above code, the result of a.equals (b) is false because the number of decimal places after 2.00 and 2.0 are different, but the result of a.compareto (b) = = 0 is true because the values of 2.00 and 2.0 are indeed equal at the mathematical level.

The CompareTo () method is very rigorous. The source code is as follows:

private int compareMagnitude(BigDecimal val) {
    // Match scales,avoid unnecessary inflation
    long ys = val.intCompact;
    long xs = this.intCompact;
    if (xs == 0)
        return (ys == 0) ? 0 : -1;
    if (ys == 0)
        return 1;

    long sdiff = (long)this.scale - val.scale;
    if (sdiff != 0) {
        // Avoid matching scales if the (adjusted) exponents differ
        long xae = (long)this.precision() - this.scale;   // [-1]
        long yae = (long)val.precision() - val.scale;     // [-1]
        if (xae < yae)
            return -1;
        if (xae > yae)
            return 1;
        if (sdiff < 0) {
            // The cases sdiff <= Integer.MIN_VALUE intentionally fall through.
            if ( sdiff > Integer.MIN_VALUE &&
                    (xs == INFLATED ||
                            (xs = longMultiplyPowerTen(xs,(int)-sdiff)) == INFLATED) &&
                    ys == INFLATED) {
                BigInteger rb = bigMultiplyPowerTen((int)-sdiff);
                return rb.compareMagnitude(val.intVal);
            }
        } else { // sdiff > 0
            // The cases sdiff > Integer.MAX_VALUE intentionally fall through.
            if ( sdiff <= Integer.MAX_VALUE &&
                    (ys == INFLATED ||
                            (ys = longMultiplyPowerTen(ys,(int)sdiff)) == INFLATED) &&
                    xs == INFLATED) {
                BigInteger rb = val.bigMultiplyPowerTen((int)sdiff);
                return this.intVal.compareMagnitude(rb);
            }
        }
    }
    if (xs != INFLATED)
        return (ys != INFLATED) ? longCompareMagnitude(xs,ys) : -1;
    else if (ys != INFLATED)
        return 1;
    else
        return this.intVal.compareMagnitude(val.intVal);
}

Next, use BigDecimal to solve the initial problem.

BigDecimal d1 = new BigDecimal("0.1");
BigDecimal three = new BigDecimal("3");
BigDecimal d2 = new BigDecimal("0.3");

d1 = d1.multiply(three);

System.out.println("d1 = " + d1);
System.out.println("d2 = " + d2);
System.out.println(d1.compareTo(d2));

The results of the program output are as follows:

d1 = 0.3
d2 = 0.3
0

D1 and D2 are both 0.3, so the result of compareto() is 0, indicating that the two values are equal.

To sum up, never use the = = operator to compare floating-point numbers, because there is a precision problem. Either use thresholds to ignore rounding, or use BigDecimal instead of double or float.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>