Home | FAQ | Contact me

BigDecimal Java

A British developer, Peter Lawry, says (and I'm paraphrasing somewhat to make his prose clearer),

"Many developers believe that BigDecimal is the only way to deal with money. They say that whenever they've replaced a double with a BigDecimal, they've fixed a bug or two. What's unconvincing about this is that perhaps they could have fixed the bug in the handling of double without resorting to the extra overhead of using BigDecimal.

"When asked to improve the performance of a financial application, I know at some time I'll be removing BigDecimal if it's there. It isn't usually the biggest performance problem, but in the process of improving the application's performance, this problem moves up the list of worst offenders."

BigDecimal sufferes from:

Benchmark

Note: JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks. You get it this way:

  <dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
  </dependency>

...or, you can get the JAR from here.

Peter's benchmark shows off the two biggest problems with BigDecimal, clarity and performance. Note the lines below whose grey background is slightly darker than elsewhere in the code. These underscore a) the simplicity of using intrinsic double (including rounding, which is necessary here) as compared to a BigDouble.

import java.util.Random;
import java.math.BigDecimal;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
public class DoubleVsBigDecimalBenchmark
{
    static final int SIZE = 1024;

    private final double[]     double_a      = new double[ SIZE ];
    private final double[]     double_b      = new double[ SIZE ];
    private final double[]     double_mid    = new double[ SIZE ];
    private final BigDecimal[] bigdouble_a   = new BigDecimal[ SIZE ];
    private final BigDecimal[] bigdouble_b   = new BigDecimal[ SIZE ];
    private final BigDecimal[] bigdouble_mid = new BigDecimal[ SIZE ];

    public MyBenchmark()
	 {
        Random rand = new Random( 1 );

        for( int i = 0; i < SIZE; i++ )
		  {
            int x = rand.nextInt( 200000 ),
				    y = rand.nextInt( 10000);

            bigdouble_a[ i ] = BigDecimal.valueOf( double_a[ i ] = x / 1e5 );
            bigdouble_b[ i ] = BigDecimal.valueOf( double_b[ i ] = ( x + y ) / 1e5 );
        }

        calculateMidPriceUsingDouble();
        caluclateMidPriceUsingBigDecimal();

        for( int i = 0; i < SIZE; i++ )
		  {
            if( double_mid[ i ] != bigdouble_mid[ i ].doubleValue())
                throw new AssertionError( double_mid[ i ] + " " + bigdouble_mid[ i ]);
        }
    }

    @Benchmark
    public void calculateMidPriceUsingDouble()
	 {
        for( int i = 0; i < SIZE; i++)
            double_mid[ i ] = round6( ( double_a[ i ] + double_b[ i ] ) / 2 );
    }

    static double round6( double x )
	 {
        final double factor = 1e6;
        return ( long ) ( x * factor + 0.5 ) / factor;
    }

    @Benchmark
    public void caluclateMidPriceUsingBigDecimal()
	 {
        for( int i = 0; i < SIZE; i++ )
            bigdouble_mid[ i ] = bigdouble_a[ i ].add( bigdouble_b[ i ] )
            .divide( BigDecimal.valueOf( 2 ), 6, BigDecimal.ROUND_HALF_UP );
    }

    public static void main( String[] args ) throws RunnerException
	 {
        Options opt = new OptionsBuilder()
                .include( ".*" + MyBenchmark.class.getSimpleName() + ".*" )
                .forks( 1 )
                .build();
        new Runner( opt ).run();
    }
}