Regular readers know that I work in a small workshop company in Luoyang, the ancient capital of nine dynasties. I hold multiple jobs, talk about business and knock on code at the same time with two new people, one of whom is known as Xiao Wang, who often makes mistakes and is written in my articles.

However, Xiao Wang’s state of mind has been very good, he did not feel criticized by me what shame, but every time after reading my article felt upgraded. Therefore, I think Xiao Wang has a great future. If I work like this for another year or two, the boss may dismiss me and keep Xiao Wang. I laughed at the thought of it.

 

That day, I was idle to be bored, ready to secretly review wang’s code, to see if you can pick some bones in the egg, did not expect, but also really picked by me.

double d1 = .0;
for (int i = 1; i <= 11; i++) {
    d1 += .1;
}
double d2 = .1 * 11;
System.out.println(d1 == d2);
Copy the code

Xiao Wang this code quite showy skills, in fact, especially.0,.1 writing method, I usually are honest written 0.0, 0.1, never thought to the decimal point in front of the 0 omission.

Normal logic says d1 should be 1.1 after 11 cycles of adding.1, d2 should be 1.1 after 11 cycles of multiplying.1 by 11, and it should print true, right? Wang should be looking forward to it, too, I think.

But I just could not contain my violent temper and shouted: “Oh my gosh, Xiao Wang, you dare to compare floating point numbers with ==, this is not looking for excitement?”

 

If the reader also thinks the output is true, you can run the above code locally and it will surprise you.

false
Copy the code

Yes, false. I’m not lying to you. How to correctly compare floating-point numbers (single-precision floats and double-precision doubles) is not just a Java-specific problem, but one that many beginners to programming languages encounter. In the memory of a computer, the use of IEEE 754 standards for storing floating-point numbers will have accuracy problems. As for the actual storage conversion process, this article does not discuss too much.

(Mainly because I’m too bad, the discussion process is boring and not interesting. I’ll leave the rigorous theoretical derivation to the real technology leaders.)

 

All you need to know is that floating-point numbers are subject to small rounding errors when stored and converted, which is why the “==” operator should not be used when comparing floating-point numbers — strict equality is required.

Looking at Wang’s code, let’s print out d1 and d2 to see what their values are.

D1:1.0999999999999999 D2:1.1Copy the code

No wonder “==” will print false, the original d1 value has some error, not 1.1 as we expected. Since “==” can’t be used to compare floating-point numbers, wang gets scolded, right?

So how to solve this problem?

For floating-point storage and conversion problems, I can’t do anything, it’s really, the bottom of the computer, can’t handle it. However, some compromise can be achieved, such as allowing a slight error between the two values (specifying a threshold), as small as 0.000000… . 1. I don’t even have to count how many zeros there are, but they’re really small, so we assume that these two floating point numbers are equal.

The first option is to use the math.abs () method to calculate the difference between two floating-point numbers, and if the difference is within the threshold, we consider them equal.

final double THRESHOLD = .0001; double d1 = .0; for (int i = 1; i <= 11; i++) { d1 += .1; }double d2 = .1 * 11; If (math.abs (d1-d2) < THRESHOLD) {system.out.println ("d1 equals d2 "); } else {system.out.println ("d1 and d2 are not equal "); }Copy the code

Math.abs() is used to return the absolute value of double, a positive value if double is less than 0, or double otherwise. In other words, the result after abs() is definitely greater than 0. If the result is less than THRESHOLD, d1 and d2 are considered equal.

The second solution is to use the BigDecimal class, which can specify the schema and precision to be rounded so that rounding errors can be addressed.

Two numbers can be compared using the compareTo() method of the BigDecimal class, which ignores the number of digits after the decimal point. For example, the number of digits 2.0 and 2.00 are different, but they have the same value.

This method returns -1 if a is less than b, 0 if it is equal, and -1 otherwise.

Note that you should never compare two BigDecimal objects using the equals() method, because the equals() method takes into account the number of digits and returns false if they are different, even though the mathematical values are equal.

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);
Copy the code

A. quals(b) is false because 2.00 and 2.0 have different number of decimal places, but a.pareto (b) == 0 is true because 2.00 and 2.0 are mathematically equivalent.

CompareTo () method comparison process is very rigorous, interested students can check the source code, which is different when the number of bits, the following method will be compared.

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); }Copy the code

Ok, now let’s use BigDecimal to solve the accuracy problem.

BigDecimal d1 = new BigDecimal("0.0"); BigDecimal pointOne = new BigDecimal("0.1"); for (int i = 1; i <= 11; i++) { d1 = d1.add(pointOne); }BigDecimal d2 = new BigDecimal("0.1"); BigDecimal eleven = new BigDecimal("11"); d2 = d2.multiply(eleven); System.out.println("d1 = " + d1); System.out.println("d2 = " + d2); System.out.println(d1.compareTo(d2));Copy the code

The output of the program is as follows:

D1 = 1.1, d2 = 1.1 0Copy the code

Both d1 and d2 are 1.1, so compareTo() results in 0, indicating that the two values are equal.

To summarize, never use the “==” operator to compare floating point numbers because of accuracy issues. Either use thresholds to ignore rounding problems, or use BigDecimal instead of double or float.

Later, I will send this article to Xiao Wang, and the students will easily give a thumb-up, which will make Xiao Wang no longer feel so lonely and cold.