Java: BigDecimal's Big Problems

In Java, the BigDecimal class has a failed abstraction around equality comparisons that will inevitably lead to bugs. There are 2 problems:

1. the method compareTo is inconsistent with the method equals

Equality (equals) of two BigDecimals takes scales (number of decimal places) into account, whereas comparison (compareTo) doesn't.

So,

new BigDecimal("1.00").equals(new BigDecimal("1.0")) 

returns false, and

new BigDecimal("1.00").compareTo(new BigDecimal("1.0"))

returns 0 (zero), indicating that they're equal.

I've seen some threads stating that equals is consistent with the way that an engineering (or scientific) quantity should work, where scale is very important. Since operations with numbers with different scales yield different results, it seems reasonable that equals returns false for these numbers. So, from this standpoint, the equals implementation is right, and compareTo should be consistent and return that the numbers are not equal, maybe ordered by the number of decimal places.

Which brings us to the second problem:

2. the method equals is incompatible with the real world meaning of equality

Engineering perspectives aside, the fact that 1.00 is not equal to 1.0 is really incompatible with at least my world view of equality. The ultimate argument for me is that the following statement is always a bug, and should never be seen in the wild:

BigDecimal.ZERO.equals(anotherBigDecimal) 

This will only be true if anotherBigDecimal has no scale (and of course, is zero). If I didn't want a scale, I guess I'd be using BigInteger. Why provide a ZERO value if it can never be equaled reliably with other values?

A possible solution would be if the scale wasn't considered for equality by default. The comparison methods should have overloaded versions that consider it. This way, the choice to compare based on scale would be explicit, eliminating this source of bugs.

Comments

  1. Hi,

    Good discussion!

    I ran into this problem as well.

    I am using a BigInteger in another class and it takes part of the equals function in that class.

    I solved the problem by using compareTo instead:

    if (value.compareTo(other.value) != 0)
    return false;

    Where value is a BigInteger. It seems to do the trick!

    ReplyDelete
    Replies
    1. Yes @Hoakz, using `compareTo` seems to be the way to go!

      Delete

Post a Comment

Popular posts from this blog

The Acyclic Visitor Pattern

C# Mixins With State

Some OO Design