64

Actually, I've found possible solution

//returns true
new BigDecimal("5.50").doubleValue() == new BigDecimal("5.5").doubleValue()

Of course, it can be improved with something like Math.abs (v1 - v2) < EPS to make the comparison more robust, but the question is whether this technique acceptable or is there a better solution?

If someone knows why java designers decided to implement BigDecimal's equals in that way, it would be interesting to read.

buræquete
  • 14,226
  • 4
  • 44
  • 89
Roman
  • 64,384
  • 92
  • 238
  • 332
  • 9
    If your BigDecimal objects are guaranteed to be always representable by doubles, then you shouldn't be using BigDecimal anyway. If they are not, then this method is going to fail. – DJClayworth Oct 05 '10 at 18:26
  • 4
    Bad solution. If doubles are appropriate to your program, use doubles. If BigDecimals are appropriate, use BigDecimals. It is almost never useful to convert back and forth. – Jay Oct 05 '10 at 20:34
  • @DJClayworth: where do you see "edited" label? – Roman Oct 06 '10 at 09:12
  • You're right, I didn't. I assumed it was edited because you answered it yourself. My apologies. – DJClayworth Oct 07 '10 at 17:56
  • Since nobody addressed your comment as to *why* `BigDecimal.equals` is specified in this way, I've asked explicitly: [Why is BigDecimal.equals specified to compare both value and scale individually?](http://stackoverflow.com/questions/14102083/why-is-bigdecimal-equals-specified-to-compare-both-value-and-scale-individually) – bacar Jan 02 '13 at 01:37

3 Answers3

109

From the javadoc of BigDecimal

equals

public boolean equals(Object x)

Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two 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).

Simply use compareTo() == 0

Colin Hebert
  • 91,525
  • 15
  • 160
  • 151
  • 1
    Because the implementation of `equals` isn't only based on the value but also on the scale, this avoid having `new BigDecimal("5.01").equals(new BigDecimal("5.0")) == false` while `new BigDecimal("5.0").equals(new BigDecimal("5.01")) == true`. All of that because [`equals()` is symmetric](http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)). – Colin Hebert Jan 01 '13 at 21:20
  • 7
    You don't need to forgo symmetry to implement `equals` as a numerical value comparison, so symmetry is not the reason. I've asked the "why" question explicitly here: [Why is BigDecimal.equals specified to compare both value and scale individually?](http://stackoverflow.com/questions/14102083/why-is-bigdecimal-equals-specified-to-compare-both-value-and-scale-individually) – bacar Jan 02 '13 at 01:33
  • 1
    @bacar: Among other things, it would be surprising for `x.equals(y)` to return true while `x.toString().equals(y.toString())` returned false. – supercat Jan 21 '13 at 20:21
  • 5
    @supercat I don't think you should ever necessarily expect those to return the same value; `toString` has no requirement on it to be consistent with `equals`. Consider `java.util.Set` implementations - they have a rigidly specified `equals` contract but a `toString` that can return items in any order. – bacar Jan 22 '13 at 09:22
  • 1
    @supercat for a concrete example from the JDK, consider `Set s1 = new LinkedHashSet(); s1.add("foo"); s1.add("bar"); Set s2 = new LinkedHashSet(); s2.add("bar"); s2.add("foo");`. `s1` and `s2` have different string representations but compare equal. – bacar Jan 22 '13 at 09:51
  • @bacar: Fair point. I'm more used to .net than Java; in .net, most mutable classes are expected to implement `Equals` with reference equality, but the .net `Dictionary` (equivalent to `HashSet`) allows one to supply an `IEqualityComparer` with equals/hashcode methods the collection should use in place of the keys' implementations. Nice example in Java, though. – supercat Jan 22 '13 at 15:38
  • @supercat I see - I do quite like the idea of there being an (optional) idiom for taking a pure value type, exposing everything about it externally then effectively allowing for different implementations of `Equals` depending on your use case for using that value type. – bacar Jan 22 '13 at 16:58
  • @bacar: It is a nice paradigm. One of my complaints with having the default "equals" implement looser semantics is that it makes it difficult to define equivalence of collections. If one deserializes two immutable collection instances and their contents are identical, it should be safe to replace all references to the second with references to the first. That's only safe, however, if the contents are equivalent. If the collections contain `BigDecimal` and code cares about the precision, such substitution becomes unsafe. – supercat Jan 22 '13 at 17:13
  • In Scala, thankfully, this works: `BigDecimal ("5.50") == BigDecimal("5.5") // true`. – Jus12 Nov 13 '17 at 21:13
  • Not that the scale matters in many applications. If you claim that an object is 50.0 meters away, it means somewhere between 49.95 and 50.04999... but if you claim that the object is 50.00000 meters away, the precision is much higher. – Andreas Lundgren Mar 20 '19 at 15:36
16

The simplest expression to compare ignoring trailing zeros is since Java 1.5:

bd1.stripTrailingZeros().equals(bd2.stripTrailingZeros())
Vasil Lukach
  • 3,658
  • 3
  • 31
  • 40
user1708042
  • 1,740
  • 17
  • 20
11

Using == to compare doubles seems like a bad idea in general.

You could call setScale to the same thing on the numbers you're comparing:

new BigDecimal ("5.50").setScale(2).equals(new BigDecimal("5.5").setScale (2))

where you would be setting the scale to the larger of the two:

BigDecimal a1 = new BigDecimal("5.051");
BigDecimal b1 = new BigDecimal("5.05");
// wow, this is awkward in Java
int maxScale = Collections.max(new ArrayList() {{ a1.scale(), b1.scale()}});
System.out.println(
  a1.setScale(maxScale).equals(b1.setScale(maxScale)) 
  ? "are equal" 
  : "are different" );

Using compareTo() == 0 is the best answer, though. The increasing of the scale of one of the numbers in my approach above is likely the "unnecessary inflation" that the compareMagnitude method documentation is mentioning when it says:

/**
 * Version of compareTo that ignores sign.
 */
private int compareMagnitude(BigDecimal val) {
    // Match scales, avoid unnecessary inflation
    long ys = val.intCompact;
    long xs = this.intCompact;

and of course compareTo is a lot easier to use since it's already implemented for you.

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 6
    only do this if you want to consider "5.051" == "5.05" as the setScale(2) will drop that extra digit, where as .compareTo(other) will compare the values without regard to scale – Gareth Davis Oct 05 '10 at 18:55
  • 3
    Gareth, `setScale` without a rounding mode parameter will not drop the extra digits, instead it will throw an ArithmeticException. So this would only work if the extra digits are all zeroes. – Jörn Horstmann Oct 05 '10 at 21:43
  • @Gareth: thanks for the feedback. at the time i wrote this long ago i was not clued into comments and notifications so I missed this. finally noticed this and updated taking your comment into account. – Nathan Hughes Apr 04 '14 at 13:57
  • Max.max(a1.scale(), b1.scale()) instead of Collections.max(new ArrayList() {{ a1.scale(), b1.scale()}}) – Maxple Oct 08 '19 at 13:43