Comparing BigDecimals for Equality

Brian Terczynski
3 min readJan 23, 2022

--

Photo by Markus Winkler on Unsplash

I had some code that subtracted two BigDecimal numbers in Kotlin (the JVM version of Kotlin, that is), and I needed to check if the result was zero to avoid a divide-by-zero error. To do that, I tried simply comparing the result of the subtraction to a BigDecimal number with value zero, like so: bdy - bdx == BigDecimal.ZERO . As a simple test of this:

val bdy = 0.3.toBigDecimal()
val bdx = 0.3.toBigDecimal()
println(bdy - bdx == BigDecimal.ZERO)

This printed false , not what I expected.

Now, when you do arithmetic on floating point numbers (floats, doubles), their results won’t always be precise. For example, on my system if I were to add the doubles 0.2 and 0.1 (println(0.2 + 0.1)), the result isn’t exactly 0.3 (I get 0.30000000000000004 ). So maybe that’s what was going on here. But I was using BigDecimal, and arithmetic operations on BigDecimals are generally not prone to such imprecision issues like IEEE-754 floating point primitives. I decided to verify that by printing the result of subtracting my two BigDecimal numbers bdy - bdx , and sure enough the result was 0.0 . Furthermore, if I looked in the debugger at the value of bdz = bdy - bdx , the value of bdz was all zeroes as expected:

So then I decided to look at the Javadoc for the == (equals()) method for BigDecimal, and it said something interesting:

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).

So when I looked at the actual value of BigDecimal.ZERO in the debugger, it came up as this:

Note that one field in particular is different than the above: the scale. It’s 0 in this case, whereas above it’s 1 . Since they’re different, the == operation does not consider them to be equal.

Instead, the Javadoc suggests that compareTo be used, because in this case I don’t actually care about the scale used to represent the number; I only care that the operation’s value is zero, regardless of what scale is used to represent it.

Sure enough, when I changed == to compareTo() like so:

val bdy = 0.3.toBigDecimal()
val bdx = 0.3.toBigDecimal()
println(BigDecimal.ZERO.compareTo(bdy - bdx) == 0)

It printed outtrue .

This makes me wonder why the equals() method is implemented like this for BigDecimal . It seems odd to me that the former doesn’t work just because it’s picky about scale. I mean, I suppose there are some use cases where we might want to know if the scales are exact, but it seems that if we really care about that it would be better if we could do the something like bdx == bdy && bdx.scale() == bdy.scale() . Then again, I guess theequals() function for most classes tends to compare all fields, or at least all the “significant” ones. So by that argument I guess having equals()compare scale as well makes sense.

I guess it’s because in Kotlin we use the == operator rather than just write out the call to theequals()method like we would in Java code. And so it might not look as natural for this situation to fail because, when reading it in code, the ==probably looks more like it would just compare values rather than doing the structural equality that equals() actually does.

Anyways, to summarize: use compareTo() when you only want to check if the values of two BigDecimals are equal.

--

--

Brian Terczynski

Documenting my learnings on my journey as a software engineer.