Java BigDecimal Gotchas

While working with BigDecimal for the last few months, I (and other colleages) have stumbled across some of the peculiarities of BigDecimal. Here’s a list of gotchas, tips, and tricks you might want to keep in mind when working with BigDecimal. There’s source code samples with the output they create, so you can copy these and try on your own.

BigDecimal has some design issues (though not as bad as Java’s Date), and monetary calculations are a field of their own as well, so keep your eyes open, write lots of tests, and think of corner cases.

Immutability

BigDecimal is immutable (which is good for design as it enforces value semantics, but bad for performance as each operations implies an object creation), thus it has to be used like this, each operation returning a new object:


// 5
System.out.println(BigDecimal.valueOf(2).add(BigDecimal.valueOf(3)));

Constructor vs. valueOf

There’s already plenty of discussions why the BigDecimal(double) constructor is evil.
Take care when converting from BigDecimal to double, the BigDecimal(double) constructor might not do what you expect. The reason is that certain values such as 0.1 are just not exactly representable as double and using this constructor will pick up that small error, too. Just use the BigDecimal(String) constructor

// 1234.56
System.out.println(BigDecimal.valueOf(1234.56));
// 1234.55999999999994543031789362430572509765625
System.out.println(new BigDecimal(1234.56));
// best option: use the string constructor
// 1234.56
System.out.println(new BigDecimal("1234.56"));

Don’t use == on BigDecimals

BigDecimals are reference types – you have to use equals (or rather compareTo, as you’ll see further below).
For certain values (0 trough 10) there’s an internal cache for those commonly used values.

// false
System.out.println(BigDecimal.valueOf(11) == BigDecimal.valueOf(11));
// true
System.out.println(BigDecimal.valueOf(11).equals(BigDecimal.valueOf(11)));
// true
System.out.println(BigDecimal.valueOf(10) == BigDecimal.valueOf(10));

equals is broken

BigDecimal.equals is problematic. Essentially, you can only use it when you’re guaranteed to always have values with the same scale. Therefore, take care when checking for equality – BigDecimal has a scale, which is also used in the equality comparison. The compareTo method, on the other hand, ignores the scale. Equals not behaving the same way as compareTo is quite counter-intuitive.

// false
System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00")));
// false
System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode());
// true
System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")) == 0);

compareTo Idiom

Use the compareTo idiom which is suggested in the compareTo javadoc. It’s just so much easier to understand.

// essentially readable as 1 < = 2
// true
System.out.println(BigDecimal.valueOf(1).compareTo(BigDecimal.valueOf(2)) <= 0);
[/codesyntax]
 
 
 
Always use a scale for divisions
When performing divisions, I strongly recommend that you specify a rounding mode. 1 / 3 = 0.33333..., which, without rounding, will throw an exception.
[codesyntax lang="java"]
//java.lang.ArithmeticException: Non-terminating decimal expansion;
//no exact representable decimal result.
try
{
	BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3));
} catch (ArithmeticException ex)
{
	System.out.println(ex.getMessage());
}
// always use a scale and the roundingmode of your choice
// 0.33
System.out.println(BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP));

Division by zero

This should be a no-brainer, but is still a topic.

// Division by zero
try
{
	BigDecimal.valueOf(1).divide(BigDecimal.ZERO);
} catch (ArithmeticException ex)
{
	System.out.println(ex.getMessage());
}

BigDecimal can't handle all double numbers

BigDecimal doesn't have a representation for NaNs etc.

// java.lang.NumberFormatException: Infinite or NaN
try
{
	System.out.println(new BigDecimal(Double.NaN));
}
catch (NumberFormatException ex)
{
	System.out.println(ex.getMessage());
}

Precision != Scale

BigDecimal has two different notions for limiting numbers, they're completely different, even thought the method signatures would suggest otherwise. This is IMO a (yet another) design flaw in BigDecimal.

  • Precision is the overall limit of, well, precision, in a number. E.g. 1234 and 12.34 have the same precision (4 decimal digits)
  • Scale is the limit of the digits after the decimal point 3.34 and 234.25 have the same scale (2 digits after the decimal point)

BigDecimal hundred = BigDecimal.valueOf(100);
BigDecimal three = BigDecimal.valueOf(3);
// 33.33
System.out.println(hundred.divide(three, 2, RoundingMode.HALF_UP));
// 33
System.out.println(hundred.divide(three, new MathContext(2, RoundingMode.HALF_UP)));

Rounding

There's various rounding modes, carefully read the javadoc.

// 1.01
System.out.println(new BigDecimal("1.005").setScale(2, RoundingMode.HALF_UP));
// 1.00
System.out.println(new BigDecimal("1.005").setScale(2, RoundingMode.HALF_DOWN));

Stay in BigDecimal Land

Sometimes it is tempting to just use double, as it's simpler to use with its operator overloading. Don't do it. When working with BigDecimals, stay within their realm.

//false
System.out.println(new BigDecimal("10e15").doubleValue() > 10e15);
//still false
System.out.println(new BigDecimal("10e15").add(BigDecimal.ONE).doubleValue() > 10e15);
//rather use this, stick with BigDecimals
//true
System.out.println(new BigDecimal("10e15").add(BigDecimal.ONE).compareTo(BigDecimal.valueOf(10e15)) > 0);

Performance

BigDecimal operations are (naturally) orders of magnitude slower than calculations with int or long (as each BigDecimal operation involves an object creation). But the BigDecimal operations per second you can expect on a modern machine still number in the millions.

// 2
System.out.println(4 / 2);
// at least a hundred times slower
// 2
System.out.println(BigDecimal.valueOf(4).divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_EVEN));

Constants

If you have numbers that you use often, you might consider drawing them into constants so that the BigDecimal isn't instanciated each time. This is ok, because they're immutable.

// 1000
System.out.println(BigDecimal.valueOf(10).multiply(HUNDRED)));
//There's some built-in constants, use them when appropriate
// 0
System.out.println(BigDecimal.ZERO);

Parsing BigDecimals

Because BigDecimals might be big numbers, there's a special way of dealing with parsing BigDecimals.

NumberFormat numberFormat = NumberFormat.getInstance();
// As the NumberFormat javadoc explains, in most locales it's ok to cast to DecimalFormat
// in production code, there should be an instanceof check
final DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
// 1.0E25
System.out.println(decimalFormat.parse("10000000000000000000000001.01"));
decimalFormat.setParseBigDecimal(true);
// 10000000000000000000000001.01
System.out.println(decimalFormat.parse("10000000000000000000000001.01"));

Signum

There's signum, use it when appropriate (IMO more concise than x.compareTo(BigDecimal.ZERO) == 0)

//-1
System.out.println(BigDecimal.valueOf(-2).signum());

General rounding topics in monetary calculations

One thing that comes along when using rounded calculations is that multiplication and division operations are not reversible, and that certain calculations just plain are not possible.

Irreversible operations

For example, when calculating with 2 decimal places with the German VAT of 19%, there's no net price with 2 decimal places so you can have a gross price of 9.99

9.99 / 1.19 = 8.3949 -> 8.43

8.39 * 1.19 = 9.9841 - > 9.98
8.40 * 1.19 = 9.996 - > 10.00

So there's just no net price for representing a gross price of 9.99. You can either live with it, calculate with a higher precision, or use some correction mechanism where errors cancel each other out over multiple calculations.

Rounding errors in sums

A related topic applies to sum calculations of rounded values. If you've got a sum that you calculate both in gross and net (assuming a VAT of 19%)

Net           Gross
0.01          0.01
0.01          0.01
0.01          0.01
-----------------------
0.03          0.03
0.03 * 1.19 = 0.04

So there's a mismatch between the summed up gross price and the gross sum calculated from the net sum.
This can be solved by tweaking one (usually the biggest) net price in the sum so that the total result is ok again.

If you don't (want to) use BigDecimal

For monetary calculations, if you can't use BigDecimal, I would tend to recommend using longs or ints over double. I've seen homegrown double calculations where 0.00499999999 would be rounded to ... you can guess it.

This entry was posted in Software. Bookmark the permalink.

15 Responses to Java BigDecimal Gotchas

  1. Pingback: Java BigDecimal | porady InformaTyczne eduIT.pl

  2. Programmer234 says:

    Here’s another trap. I have a BigDecimal that grows very large, like over 150 integer digits and 70 decimal places… in a loop to calculate a math function.

    For efficiency, I want to store each value in a vector for the next loop so I can re-use the calculated values rather than re-compute them. It didn’t work…

    Found out that value retrieved from vector IMMEDIATELY after adding the BigDecimal to the vector did not match because somehow, the BigDecimal in the vector was given a scale=100, while the variable’s scale was = 150.

    Don’t know why the scale doesn’t stay with the object.

    • Sti says:

      Could you provide a code example? Can’t think of why this should happen (adding a BigDecimal to an ArrayList or the likes would certainly not change it’s value (it’s immutable anyway))

  3. lin says:

    I found something weird.
    I have this BigDecimal property in my POJO.
    private BigDecimal count
    The values stored are 20,40,40

    When I sum up the values using Hibernate, the return result is 100.00 instead of 100.
    Could you explain the logic here?

    Thanks

  4. Miss Take says:

    Hi,

    Mistake in this example (quotes around string “1234.56” were left out):

    // best option: use the string constructor
    // 1234.55999999999994543031789362430572509765625
    System.out.println(new BigDecimal(1234.56));

  5. Programmer234 says:

    Sorry but I never saw your reply to the May 19 post. I no longer have the code that did that, I did a work-around.

    But have another BigDecimal issue – possibly 2 issues, I don’t know. If I write a small test program, it will work fine as it does with my real test program also – but it eventually fails. Below are the data and code snippets. I wrote a package of math functions to perform unlimited-precision calculations (not new under the sun). The problem happens to occur for the calculation of gamma(141.5) — very large numbers! The tests are to 70 decimal places.

    If you email me directly, I will try to send you actual code.

    This diagnostic result happens in 2 consecutive calls to LN10() which produces natural log of ten (= approx 2.302):
    “pure” means the actual BigDecimal value, not scaled, altered by me.

    LN10(). Prec = 622, 632, dec LN10 = 1500
    Returning (DEC = 622) LN10 pure = 2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354867623653981586500
    scale = 1515, LN10 = 2.30258509299404568401799145468436420760110148862877297603332790096757260967735248023599720508959829834196778404228624863340952546508280675666628736909878168948290720832555468084379989482623319852839350530896537773262884616336622228769821988674654366747440424327436515504893431493939147961940440022210510171417480036880840126470806855677432162283552201148046637156591213734507478569476834636167921018064450706480002775026849167465505868569356734206705811364292245544057589257242082413146956890167589402567763113569192920333765871416602301057030896345720754403708474699401682692828084811842893148485249486448719278096762712757753970276

    LN10(). Prec = 622, 632, dec LN10 = 1500
    Returning (DEC = 622) LN10 pure = 2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354867623653981586500
    scale = 1515, LN10 = 2414435.46647132444716484940758711188334953259454040425211712283708497341676503955431594100532403062528222721111992434544702602657407466917767810894434012410883122890887717682502046831851731230238010874822285367992136902499459789990154544861596514377266564090379566071682059134822188736013339658836729407913504255547152363816454252769498819106998638032871014150603109788532874913848467741355454413949437949463997975389826553392624310281640981806927530752857124105663605730713001873808447983468064370225386894774573930035631898882386535174433177229166610485769623017566399818831314901859662973526066068965502452265749591058292674627136846361

    What is interesting is the last value is the product of the correct ln(10) value and a power of 2, i.e. 2^n * ln(10). Almost suggests a sign bit being misinterpreted.

    public static BigDecimal LN10()
    {
    System.out.println(“LN10(). Prec = ” + DEC_PLACES + “, ” + MAX_PLACES + “, dec LN10 = ” + decLN10);

    if (DEC_PLACES > decLN10 || MAX_PLACES > maxLN10) //// ** this was not true in the above case **
    {
    Big origPrec = GetPrecision();
    decLN10 += CONSTANT_BUMP;
    maxLN10 += CONSTANT_BUMP;
    SetPrecisionAndExponent(maxLN10,decLN10,NO_MAX_POWER);

    constLN10 = ln(TEN);
    System.out.println(” Recomputed: DEC = ” + DEC_PLACES + “, LN10 = ” + constLN10);
    ResetPrecision(origPrec);
    }
    System.out.println(” Returning (DEC = ” + DEC_PLACES + “) LN10 pure = ” + constLN10);
    System.out.println(” scale = ” + constLN10.scale() + “, LN10 = ” + constLN10.setScale(MAX_PLACES,ROUND_DOWN));

    return constLN10.setScale(MAX_PLACES,ROUND_DOWN);
    }

    ———

    Earlier, I had this issue: ……… again, the wrong result is an exact multiple of 2^n which is why I suspect the sign bit.
    (this value is gamma(191.5)

    Big.round(): places = 855, scale = 855, x = 133697194382348382096607788465183338193565456046018844247569458972750894312486998024824325724736982326851035901784425686447370049387786067826320738076260459479550508892238045973237342344575773985256941526172255477095635860216762897905781270700308727774042935703399597077388908867710719254524087853583954435487159392351270532689241809693305754018122208830.052774045676177540780577482760683741748742246323494223099197616448289164739956466536268891422652809047147569277704515412933126019046066397578384697103184431230138573070468750241143655744734422168158922372885444168288091506711722816245533591329280471874834400953949451738736033399493388622631665559722933923768787284147719231793497032011813869500003685276532172640163821615230058054271713004217387913643392556323142929348904523097381245113054180156050975184013059949054757848951784682596943262959529317481041437346718980025699597824981108432944479090272482077039776066834988818947780715418504107311388589119018030951267556805276626512099618971055427762408013143468189421010057934602622692679261152349637233190683346158625891935005664124473186230139276906034546782956602081031912636184588768340629591640495282901218867842538624864380172305081855847777581526
    scale = 0, scaled = 8761979331041583569083288024854255251853505727431890976608712063238202609663147902554887010696362873772509488859344121787014843556677947741065755890565805472451822150761712580902082467894117923897798919859224934946939591735165773277153281356615432783399677834257995994063759531554289697064490621572478037884086477937132865630322151240060485895331657077886338599857433971312595925910204169699245571855056517405029014991555078700397786990920918068274974493713863100183643122101985346784203007431697019509354294885098361524746240015803590622886915091212463136629420469012928364983859466485467289441355725004789147300918031269149804684869198716772788834122002197628111243453904927574818621489926233751552241518282812466145776213375717084644750983444390734308533374571193495017809806825709977279729118750706956709659479896821252610388904160958673273681315713350437531637954575074964248843057961922261449381660097385400878764316097827238561756965667085176759162576503965676422270602790608995096960628887088513837171549370331261895315156802117480787428058880385825714384623773851706453852531204061474732778407651313880057967843873982507426524993209521971500917751498860214279722928611319112018972185844504839951582887936

    private static BigDecimal round(BigDecimal x, int decPlaces)
    {
    System.out.println(” Big.round(): places = ” + decPlaces + “, scale = ” + x.scale() + “, x = ” + x);
    BigDecimal scaled = x.movePointRight(decPlaces);
    System.out.println(” scale = ” + scaled.scale() + “, scaled = ” + scaled);

    BigDecimal intx = RoundIntD(scaled); ////// my rounding function to the given # decimal places
    BigDecimal rounded = intx.movePointLeft(decPlaces);
    return rounded;
    }

    public static BigDecimal RoundIntD(BigDecimal x)
    {
    BigDecimal half = x.signum() >= 0 ? HALF : NEG_HALF; //HALF = BigDecimal(“0.5”) or BigDecimal(“-0.5”)
    return new BigDecimal( x.add(half).toBigInteger() );
    }

    • Sti says:

      Sorry, but I can’t really make heads or tails of it – if you really think it’s a bug (or bugs) in the BigDecimal implementation, you should take a look into the relevant BigDecimal code to find out what’s wrong, and report it to Oracle with a test case to demonstrate the problem

      • Programmer234 says:

        Thanks. I didn’t think it would be easy to read. But I discovered something.

        I backtracked to a version where the constants are static variables rather than functions and it still messed up. I never used the java debugger before (jdb) but tried, hoping to trace the variable.

        Surprise! The program worked and completed faster than originally, which was the idea. I suspect it’s something about the java/javac versions but not sure. Unless the data got relocated or rearranged by the debugger’s jvm.

        • Programmer234 says:

          Thought this might interest you.

          The change above wasn’t sufficient. So analyzing further, I found that printing the BigDecimal worked using .toString() but not .toPlainString(), and .toBigInteger() prints a bad value also, but the BigDecimal seems to really be OK.

          I finally stumbled onto a workaround that corrects all the problems… it’s the following one statement. I leave it to interpretation what it may mean. I’ve submitted a Java bug report days ago.

          x = new BigDecimal( x.toString() ); // patch to solve problem

  6. Aither says:

    Hello,
    Thanks for that interesting article.
    I think your example about the compareTo idiom is incomplete: it does not show the idiom. It should be:

    // essentially readable as 1 <= 2
    // true
    System.out.println(BigDecimal.valueOf(1).compareTo(BigDecimal.valueOf(2)) <= 0);

  7. Shivanand Naik says:

    I have a question. If a big decimal zero is divided by a negative big decimal the resulting zero would have sign bit of negative? If yes then how to compare such numbers with ZERO?

Comments are closed.