Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is BigDecimal natural ordering inconsistent with equals?

Tags:

From the Javadoc for BigDecimal:

Note: care should be exercised if BigDecimal objects are used as keys in a SortedMap or elements in a SortedSet since BigDecimal's natural ordering is inconsistent with equals.

For example, if you create a HashSet and add new BigDecimal("1.0") and new BigDecimal("1.00") to it, the set will contain two elements (because the values have different scales, so are non-equal according to equals and hashCode), but if you do the same thing with a TreeSet, the set will contain only one element, because the values compare as equal when you use compareTo.

Is there any specific reason behind this inconsistency?

like image 820
Jeets Avatar asked Nov 06 '13 17:11

Jeets


People also ask

What happens if equals () is not consistent with compareTo () method?

But if compareTo() is "inconsistent with equals" then this code can throw the exception, because a. compareTo(b) can return zero when a. equals(b) is false.

How do you know if two BigDecimal values are equal?

equals() method checks for equality of a BigDecimal value with the object passed. This method considers two BigDecimal objects equal if only if they are equal in value and scale.

How accurate is BigDecimal?

BigDecimal precision is de facto unlimited since it is based on an int array of arbitrary length. Though operations with double are much faster than with BigDecimal this data type should never be used for precise values, such as currency.


2 Answers

From the OpenJDK implementation of BigDecimal:

/**      * 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).      *      * @param  x {@code Object} to which this {@code BigDecimal} is       *         to be compared.      * @return {@code true} if and only if the specified {@code Object} is a      *         {@code BigDecimal} whose value and scale are equal to this       *         {@code BigDecimal}'s.      * @see    #compareTo(java.math.BigDecimal)      * @see    #hashCode      */     @Override     public boolean equals(Object x) {         if (!(x instanceof BigDecimal))             return false;         BigDecimal xDec = (BigDecimal) x;         if (x == this)             return true;     if (scale != xDec.scale)         return false;         long s = this.intCompact;         long xs = xDec.intCompact;         if (s != INFLATED) {             if (xs == INFLATED)                 xs = compactValFor(xDec.intVal);             return xs == s;         } else if (xs != INFLATED)             return xs == compactValFor(this.intVal);          return this.inflate().equals(xDec.inflate());     } 

More from the implementation:

 * <p>Since the same numerical value can have different  * representations (with different scales), the rules of arithmetic  * and rounding must specify both the numerical result and the scale  * used in the result's representation. 

Which is why the implementation of equals takes scale into consideration. The constructor that takes a string as a parameter is implemented like this:

    public BigDecimal(String val) {         this(val.toCharArray(), 0, val.length());     } 

where the third parameter will be used for the scale (in another constructor) which is why the strings 1.0 and 1.00 will create different BigDecimals (with different scales).

From Effective Java By Joshua Bloch:

The final paragraph of the compareTo contract, which is a strong suggestion rather than a true provision, simply states that the equality test imposed by the compareTo method should generally return the same results as the equals method. If this provision is obeyed, the ordering imposed by the compareTo method is said to be consistent with equals. If it’s violated, the ordering is said to be inconsistent with equals. A class whose compareTo method imposes an order that is inconsistent with equals will still work, but sorted collections containing elements of the class may not obey the general contract of the appropriate collection interfaces (Collection, Set, or Map). This is because the general contracts for these interfaces are defined in terms of the equals method, but sorted collections use the equality test imposed by compareTo in place of equals. It is not a catastrophe if this happens, but it’s something to be aware of.

like image 93
Nir Alfasi Avatar answered Oct 08 '22 12:10

Nir Alfasi


The behaviour seems reasonable in the context of arithmetic precision where trailing zeros are significant figures and 1.0 does not carry the same meaning as 1.00. Making them unequal seems to be a reasonable choice.

However from a comparison perspective neither of the two is greater or less than the other and the Comparable interface requires a total order (i.e. each BigDecimal must be comparable with any other BigDecimal). The only reasonable option here was to define a total order such that the compareTo method would consider the two numbers equal.

Note that inconsistency between equal and compareTo is not a problem as long as it's documented. It is even sometimes exactly what one needs.

like image 37
assylias Avatar answered Oct 08 '22 12:10

assylias