Fun with BigDecimal

So you've got the idea for the next big FinTech company. Since this is about money, you decided to use a serious language, Java. And for representing your currency amounts, BigDecimal seemed like a safe choice.

Now time for the small print...

BigDecimal.equals() does what??

A big trap to fall into with BigDecimal is how equals() works. Even when you know it, it still comes to bite you regularly.

BigDecimal has a scale, which is taken into account by equals(). As such:

new BigDecimal("2.4").equals(new BigDecimal("2.40"));  

is false. First has a scale of 1, second of 2.

The same applies to hashCode():

assert new BigDecimal("2.4").hashCode() != new BigDecimal("2.40").hashCode();  

BigDecimal also has compareTo(), where equality tests work as expected:

assert new BigDecimal("2.4").compareTo(new BigDecimal("2.40")) == 0;  

Implications for Lombok

I like to use Lombok annotations to reduce boilerplate code in Java. Quite nice is the @EqualsAndHashCode, which generates equals() and hashCode() methods. Of course an IDE like IntelliJ supports generating these methods for you, but that remains impractical in term of maintenance: Every time you change the members of the class, you have to remember to update equals() and hashCode().

Unfortunately, Lombok implements @EqualsAndHashCode for BigDecimal fields with equals. In particular it makes testing Lombok annotated classes a pain.

Taking an example with the @Data annotation, which in addition to equals() and hashCode() generates getters, setters, constructors and toString (why this is cool is a topic for another post).

import lombok.Data;  
import java.math.BigDecimal;

@Data
public class MyDataObject {  
    private String reference;
}

This allows to write code like this:

MyDataObject object1 = new MyDataObject("ref");  
MyDataObject object2 = new MyDataObject("ref");  
assert object1.equals(object2);  

Nice when your class has lots of fields. But now adding a BigDecimal to the mix:

@Data
public class MyDataObject {  
    private String reference;
    private BigDecimal amount;
}

MyDataObject object1 = new MyDataObject("ref", new BigDecimal("2.4"));  
MyDataObject object2 = new MyDataObject("ref", new BigDecimal("2.40"));  
assert !object1.equals(object2);  

The two objects are not equals anymore according to the equals() method.

Adding a database

Let's look where this really hurts: When storing BigDecimal in a database and reading it again. Using Java persistence as an example, let's have an entity corresponding to a table:

import javax.persistence.Entity;

@Entity
public class MyEntity {  
    public BigDecimal amount;
}

We save it like:

MyEntity entity = new MyEntity();  
entity.amount = new BigDecimal("2.4");  
myEntityRepository.save(entity);  

And later we read this object from DB again:

MyEntity sameEntity = myEntityRepository.findOne(...);  

Ouch:

assert !sameEntity.equals(entity);  

In fact, the object read from DB will be more like "2.4000000000000000". The column in the DB was for example set up as:

CREATE TABLE my_entity (  
amount DECIMAL(32, 16)  
);

Meaning a precision of 32 and a scale of 16. So when the BigDecimal is read back from DB, it's set with a scale of 16, while it was initially saved with a scale of 1. equals() shows them as different.

BigDecimal in Groovy

Now comes a new actor in this story, Groovy. In Groovy, BigDecimal is the default type for non-integer floating types. It even has a practical syntax to create a BigDecimal, with the ‘G' suffix:

assert 9203.10291G instanceof BigDecimal  

This compares well versus the equivalent in Java:

new BigDecimal("9203.10291")  

Where it gets fun is comparaison. Groovy supports operator overloading, meaning writing

myBigDecimal1 == myBigDecimal2  

in Groovy doesn't do the same as in Java.

But what does it do? Is following true or false?

2.4G == 2.40G  

For ==, Groovy normally calls equals(), except if the first class implements java.util.Comparable. In that case, it uses compareTo(). So our statement is actually true!

assert 2.4G == 2.40G  

In practice, if like us you like to use Spock to test your Java code, since Spock tests are in Groovy this particularity of Groovy will probably make your tests much easier to write than in Java.