JUnit gotcha with BigDecimal

Anyone working with Java and numbers will be familiar with the idiosyncrasies of determining whether two BigDecimals are equal. As defined by the javadoc for the equals method on the BigDecimal class it defines two numbers as being equal if:

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 it comes to working out if two BigDecimals are equal and you don’t care about scale then you should use the compareTo method:

  boolean equal = new BigDecimal("1.00").compareTo("1.0") == 0;

Now the fun comes when you write a JUnit test to check for the equality of two BigDecimals. Take the following two test methods:

	@Test
	public void testBigDecimalEquality1()
	{
		junit.framework.Assert.assertEquals(
                   new BigDecimal("1.00"), new BigDecimal("1.01"));
	}

	@Test
	public void testBigDecimalEquality2()
	{
		org.junit.Assert.assertEquals(
                  new BigDecimal("1.00"), new BigDecimal("1.01"));
	}

Given what we know about BigDecimal and it’s implementation of equals (as you would assume that’s what the JUnit assertion would invoke) we would expect both tests to fail.

However rather surprisingly the second test method testBigDecimalEquality2() passes. It shouldn’t as the numbers are quite clearly not the same value.

To find out why we get this strange behaviour I downloaded the source code for the version* of JUnit I was using and had a look at the implementations of the assertions.

For the junit.framework.Assert version we have:

	/**
	 * Asserts that two objects are equal. If they are not
	 * an AssertionFailedError is thrown with the given message.
	 */
	static public void assertEquals(String message, Object expected, Object actual) {
		if (expected == null && actual == null)
			return;
		if (expected != null && expected.equals(actual))
			return;
		failNotEquals(message, expected, actual);
	}

This is what I would have expected the implementation to look like, it simply delegates down to the objects equals method.

For the org.junit.Assert version we have:

	/**
	 * Asserts that two objects are equal. If they are not, an {@link AssertionError}
	 * is thrown with the given message. If expected and actual
	 * are null, they are considered equal.
	 * @param message the identifying message or null for the {@link AssertionError}
	 * @param expected expected value
	 * @param actual actual value
	 */
	static public void assertEquals(String message, Object expected, Object actual) {
		if (expected == null && actual == null)
			return;
		if (expected != null && isEquals(expected, actual))
			return;
		else if (expected instanceof String && actual instanceof String) {
			String cleanMessage= message == null ? "" : message;
			throw new ComparisonFailure(cleanMessage, (String)expected, (String)actual);
		}
		else
			failNotEquals(message, expected, actual);
	}

	private static boolean isEquals(Object expected, Object actual) {
		if (expected instanceof Number && actual instanceof Number)
			return ((Number) expected).longValue() == ((Number) actual).longValue();
		return expected.equals(actual);
	}

This implementation delegates down to the private method isEquals(). Now that checks if the object is an instance of the Number class (which BigDecimal is) and then retrieves the long values from the expected and actual and does a simple comparison. This will lose any precision following the decimal point and is why the test passed incorrectly as 1.00 and 1.01 have the same whole number part.

Obviously this begs the question of why do we have two implementations of the Assert class supplied with JUnit. For an answer to this I found a discussion on stackoverflow.com.

This is more than a little worrying as you may have tests that pass successfully but because of a bug in the test framework your code is actually incorrect. Not a good position to be in, especially when you are dealing with numbers and calculations. Imagine if you were writing a financial application and you were only accurate up to the decimal point, I don’t think the client would be best pleased.

With this in mind I tend to use the assertTrue method and use either the equals or compareTo method as appropriate (depending on if you are concerned about scale or not), for example:

	@Test
	public void testBigDecimalEquality3()
	{
		Assert.assertTrue(new BigDecimal("1.00")
				.equals(new BigDecimal("1.00")));

		Assert.assertTrue(new BigDecimal("1.00")
				.compareTo(new BigDecimal("1.0")) == 0);
	}

*Note: The version of JUnit I ran the examples on is 4.3.1

Leave a Reply

You must be logged in to post a comment.