From the Javadoc for BigDecimal
:
Note: care should be exercised if
BigDecimal
objects are used as keys in aSortedMap
or elements in aSortedSet
sinceBigDecimal
'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?
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With