[advanced Java interview] Why is it forbidden to use BigDecimal's equals method for equivalence comparison?

Keywords: Java Interview equals BigDecimal


BigDecimal is a type provided by the java.math package that can be used for precise operations. For example, in scenarios such as amount representation and amount calculation, double, float and other types cannot be used, but BigDecimal with better accuracy support should be used, and many internal methods, such as addition, subtraction, multiplication and division, can be called directly.

Of course, in addition to using BigDecimal to represent numbers and carry out numerical operations, the code often needs to judge the equality of numbers. However, it should be noted that the compareTo() method should be used for the equivalence comparison of BigDecimal, not the equals() method. This is because BigDecimal is an object, so '= =' cannot be used to judge whether the values of two numbers are equal.

What about using equals for equivalence comparison?

Let's start with a code:

package com.seckill.secondkill;

import java.math.BigDecimal;

public class BigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal bigDecimal = new BigDecimal(1);
        BigDecimal bigDecimal1 = new BigDecimal(1);
        System.out.println(bigDecimal.equals(bigDecimal1));

        BigDecimal bigDecimal2 = new BigDecimal(1);
        BigDecimal bigDecimal3 = new BigDecimal(1.0);
        System.out.println(bigDecimal2.equals(bigDecimal3));

        BigDecimal bigDecimal4 = new BigDecimal("1");
        BigDecimal bigDecimal5 = new BigDecimal("1.0");
        System.out.println(bigDecimal4.equals(bigDecimal5));
    }
}

The execution results are:

true
true
false

What is the equals principle of BigDecimal?

It is found from the above code that when using the equals method of BigDecimal to compare 1 and 1.0, it is sometimes true (when using int and double to define BigDecimal) and sometimes false (when using String to define BigDecimal). Why does this happen?

In the source code of BigDecimal, there is a comment on the equals method:

     * Compares this {@code BigDecimal} with the specified
     * {@code Object} for equality.  Unlike {@link
     * #compareTo(BigDecimal) compareTo}, this method considers two
     * {@code BigDecimal} objects equal only if they are equal in
     * value and scale (thus 2.0 is not equal to 2.00 when compared by
     * this method).

The equals method is different from compareTo. The equals method compares two parts: value and scale.

Therefore, the scales of the two BigDecimal objects (BigDecimal 4 and BigDecimal 5) defined in the example code are different, and the result of using equals is false.

We run the sample code again and debug to the location of scale comparison, as shown below:

When comparing bigDecimal4 and bigDecimal5, their scales are different, so the result is false.

Why are the scales different?

Why are the scales of bigDecimal2 and bigDecimal3 the same (when BigDecimal is defined with int and double), but bigDecimal4 and bigDecimal5 are different (when BigDecimal is defined with String)?

Here is a brief introduction to scaling:
First, BigDecimal has the following four construction methods:

BigDecimal(int)
BigDecimal(double)
BigDecimal(long)
BigDecimal(String)

The scales of BigDecimal created by these four methods are different. Of which:
The simplest ones are BigDecimal(long) and BigDecimal(int). Because they are integers, the scale is 0:

    public BigDecimal(int val) {
        this.intCompact = val;
        this.scale = 0;
        this.intVal = null;
    }
    public BigDecimal(long val) {
        this.intCompact = val;
        this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
        this.scale = 0;
    }

For BigDecimal(double), when using new BigDecimal(0.1) to create a BigDecimal, the created value is not exactly equal to 0.1, but 0.100000000000055511123125782181583404541015625. This is because double itself represents only an approximation.

Therefore, whether new BigDecimal(0.1) or new BigDecimal(0.10) is used, its approximate value is 0.10000000000000000005551231257827021181583404541015625, and its scale is the number of digits of this number, that is, 55

For new BigDecimal(1.0), it is essentially an integer, so the scale of the created number is 0. Because the scales of new BigDecimal(1.0) and new BigDecimal(1.00) are the same, the result is true when using equals comparison.

When we use new BigDecimal("0.1") to create a BigDecimal, the created value is exactly equal to 0.1, so its scale is 1. If we use new BigDecimal("0.10000"), the created number is 0.10000, and the scale is 5. Because new BigDecimal("1.0") and new BigDecimal("1.00") The scale of is different, so the result is false when equals.

How do I compare BigDecimal?

BigDecimal provides the compareTo method, which can only compare the values of two numbers. If the two numbers are equal, it returns 0.

        BigDecimal bigDecimal4 = new BigDecimal("1");
        BigDecimal bigDecimal5 = new BigDecimal("1.000");
        System.out.println(bigDecimal4.compareTo(bigDecimal5)); // The result is 0

The source code of compareTo method is as follows:

    /**
     * Compares this {@code BigDecimal} with the specified
     * {@code BigDecimal}.  Two {@code BigDecimal} objects that are
     * equal in value but have a different scale (like 2.0 and 2.00)
     * are considered equal by this method.  This method is provided
     * in preference to individual methods for each of the six boolean
     * comparison operators ({@literal <}, ==,
     * {@literal >}, {@literal >=}, !=, {@literal <=}).  The
     * suggested idiom for performing these comparisons is:
     * {@code (x.compareTo(y)} &lt;<i>op</i>&gt; {@code 0)}, where
     * &lt;<i>op</i>&gt; is one of the six comparison operators.
     *
     * @param  val {@code BigDecimal} to which this {@code BigDecimal} is
     *         to be compared.
     * @return -1, 0, or 1 as this {@code BigDecimal} is numerically
     *          less than, equal to, or greater than {@code val}.
     */
    @Override
    public int compareTo(BigDecimal val) {
        // Quick path for equal scale and non-inflated case.
        if (scale == val.scale) {
            long xs = intCompact;
            long ys = val.intCompact;
            if (xs != INFLATED && ys != INFLATED)
                return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
        }
        int xsign = this.signum();
        int ysign = val.signum();
        if (xsign != ysign)
            return (xsign > ysign) ? 1 : -1;
        if (xsign == 0)
            return 0;
        int cmp = compareMagnitude(val);
        return (xsign > 0) ? cmp : -cmp;
    }

summary

  1. BigDecimal is a very easy-to-use class for representing high-precision numbers. It provides many rich methods, but you need to be careful when using equals for comparison.
  2. If you want to compare the values of two BigDecimal, you can use the compareTo method.

Original text: 17 questions on the soul of Java development

Posted by jannz on Sat, 18 Sep 2021 15:02:53 -0700