58

I am performing a simple multiplication with BigDecimal and I have found some strange behaviour when multiplying by zero (multiplying by zero is correct in this use-case).

Basic maths tells me that anything multiplied by zero will equal zero (see:Zero Product Property and Multiplication Properties)

However, the following code will consistently fail with the same error:

assertEquals(new BigDecimal(0), new BigDecimal(22.3).multiply(new BigDecimal(0)));
java.lang.AssertionError: 
Expected :0
Actual   :0E-48

Is this an inaccuracy with BigDecimal or is there some niche branch of maths that I'm missing somewhere?

Notes: JDK 1.6.0_27 running in IntelliJ 11

Mechanical snail
  • 29,755
  • 14
  • 88
  • 113
Richard
  • 9,972
  • 4
  • 26
  • 32
  • 3
    Yes look into numerical analysis and especially approximation and truncation error – Martin Larsson Aug 14 '12 at 09:26
  • Or in `double` you could write `assertEquals(0, 23.3 * 0, 0);` ;) – Peter Lawrey Aug 14 '12 at 09:29
  • 11
    And also look into `BigDecimal.ZERO`. – user207421 Aug 14 '12 at 10:41
  • 3
    @MartinLarsson The point of `BigDecimal` is that it doesn't suffer from approximation or truncation error (for non-recurring decimals). The problem is due to misuse of the interfaces, as described in the answers. – OrangeDog Aug 14 '12 at 13:41
  • 2
    BigDecimal is a classic example in Java where the contract of compareTo is not consistent with equals and you seemed to have encountered that . – Geek Aug 16 '12 at 07:50

3 Answers3

84

You can't use the equals() method to compare BigDecimals, like this assertion does. That is because this equals function will compare the scale. If the scale is different, equals() will return false, even if they are the same number mathematically.

You can however use compareTo() to do what you want:

As @assylias points out, you should also use the new BigDecimal("22.3") constructor to avoid double precision issues.

BigDecimal expected = BigDecimal.ZERO;
BigDecimal actual = new BigDecimal("22.3").multiply(BigDecimal.ZERO);
assertEquals(0, expected.compareTo(actual));

There is also a method called signum(), that returns -1, 0 or 1 for negative, zero, and positive. So you can also test for zero with

assertEquals(0, actual.signum());
Keppil
  • 45,603
  • 8
  • 97
  • 119
47

There are 2 issues with your code:

  • you should compare BigDecimal with compareTo instead of equals, as advised by the other answers
  • but you should also use the string constructor: new BigDecimal("22.3") instead of the double constructor new BigDecimal(22.3) to avoid double precision issues

In other words, the following code (which correctly uses compareTo) still returns false:

BigDecimal bd = new BigDecimal(0.1).multiply(new BigDecimal(10));
System.out.println(bd.compareTo(BigDecimal.ONE) == 0);

because 0.1d * 10d != 1

assylias
  • 321,522
  • 82
  • 660
  • 783
  • @assylias "but you should also use the string constructor: new BigDecimal("22.3") instead of the double constructor new BigDecimal(22.3) to avoid double precision issues" . can you explain the double precision issue? – Geek Aug 16 '12 at 07:49
  • 1
    @Geek try this `System.out.println(new BigDecimal("0.1"));` and this `System.out.println(new BigDecimal(0.1d));`. The first one (String constructor) prints `0.1`, whereas the second one (double constructor) prints `0.1000000000000000055511151231257827021181583404541015625`. This happens because a double can't exactly represent 0.1. More about it [on this page for example](http://stackoverflow.com/questions/4937402/moving-decimal-places-over-in-a-double). – assylias Aug 16 '12 at 08:26
  • @assylias +1 for the clarification. – Geek Aug 16 '12 at 13:08
20

equals() on BigDecimal checks the internal state of BigDecimal for comparison

Refer the code below

public boolean equals(Object x) {
    if (!(x instanceof BigDecimal))
        return false;
    BigDecimal xDec = (BigDecimal) x;
    if (x == this)
        return true;
    if (scale != xDec.scale)
        return false;
    long s = this.intCompact;
    long xs = xDec.intCompact;
    if (s != INFLATED) {
        if (xs == INFLATED)
            xs = compactValFor(xDec.intVal);
        return xs == s;
    } else if (xs != INFLATED)
        return xs == compactValFor(this.intVal);

    return this.inflate().equals(xDec.inflate());
}

if you want to compare the values use compareTo()

Change your code to

assertEquals(0 , new BigDecimal(0).compareTo(new BigDecimal(22.3).multiply(new BigDecimal(0)));

Update:

Use constructor taking String as a parameter for BigDecimal for accuracy in precision check the related links below


Also See

Community
  • 1
  • 1
jmj
  • 237,923
  • 42
  • 401
  • 438