Here are some (slightly simplifying) hints or rules for the usage of BigDecimal.
- Use
BigDecimal.valueOf(double)
ornew BigDecimal(String)
to create BigDecimal objects.
Never usenew BigDecimal(double)
, unless you know what you are doing. Be aware that 0.1 is an infinite decimal number, 0.5 is not, sonew BigDecimal(0.5)
is fine, but notnew BigDecimal(0.1)
. And if you do not want to think about the difference every time, do not usenew BigDecimal(...)
at all. - If you want to compare BigDecimal objects, be aware: equals() compares the value and the scale, that means 1.001 is not equal to 1.00100. Use signum() and compareTo().
- Use setScale to round the BigDecimal value.
- Use a Context for rounding.
- Be aware that setScale or a division may throw an ArithmeticException, so always use a RoundingMode or a RoundingContext.
package testdrive; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import org.junit.Test; public class DoubleCalcTest { @Test public void addDouble() { /** * This is actually a very old example which shows that calculations with double values can lead to unexpected * results. */ double a = 0.1; double sum = 0; for ( int i = 0; i < 10; i++ ) { sum += a; } // sum should be 1, but it isn't assertTrue( sum != 1 ); /** * There is no exact binary representation for 0.1, so repeated calculations with 0.1 will probably have wrong * results. */ } @Test public void createBigDecimalCorrectly() { /** * This shows the usage of the static valueOf method. In such a way, a BigDecimal object can be created from a * double value. Another possibility is the constructor with the String which is shown here just for fun. I would * recommend to always use the static valueOf method. */ BigDecimal a = BigDecimal.valueOf( 0.1 ); BigDecimal sum = new BigDecimal( "0" ); // Just to show this possibility. // OR: BigDecimal sum = BigDecimal.ZERO; for ( int i = 0; i < 10; i++ ) { sum = sum.add( a ); } BigDecimal expected = BigDecimal.valueOf( 1.0 ); /** * If the two BigDecimal objects may have different scale, then use signum() and compareTo(). */ assertTrue( expected.signum() == sum.signum() ); assertTrue( expected.compareTo( sum ) == 0 ); // == 0 means, they are equal. } @Test public void createBigDecimalInAWrongWay() { /** * Never use the constructor with double values, unless the binary representation of the double values is what you * want. So actually it is better to use the constructor with a String or the static value of method. * This example shows the problem with the constructor which has a double as parameter. */ BigDecimal a = new BigDecimal( 0.1 ); BigDecimal sum = new BigDecimal( 0.0 ); for ( int i = 0; i < 10; i++ ) { sum = sum.add( a ); } BigDecimal expected = new BigDecimal( 1.0 ); /** * The value of the expected variable (i.e. 1.0) is not the same as the value of the sum variable. The reason is * that there is no exact representation of 0.1 in the binary system, so the BigDecimal does not represent 0.1 but * something similar. */ assertFalse( expected.equals( sum ) ); /** * If the two BigDecimal objects may have different scale, then use signum() and compareTo(). */ assertTrue( expected.signum() == sum.signum() ); assertTrue( expected.compareTo( sum ) != 0 ); // != 0 means, they are different. /** * A hint to JUnit: * * If you use JUnit, then be aware that assertEquals does use the Number.longValue() method to compare BigDecimal * objects, so the result is often wrong. So please use the assertTrue or assertFalse methods. */ assertEquals( expected, sum ); } }
No comments:
Post a Comment