0

I'd like to round my large double so the first thing I decided to do, was to convert it into a BigDecimal in the following way.

    BigDecimal amount = BigDecimal
        .valueOf(getAmount())
        .setScale(2, RoundingMode.HALF_UP);
    System.out.println(amount);

In my example, getAmount() returns 123456789123123424113.31.

Therefore, I expect the exact same value to be printed out by my snippet.

Instead, I get the following value:

123456789123123430000.00

Can someone explain why BigDecimal is returning an approximation of my double?

ptk
  • 6,835
  • 14
  • 45
  • 91
  • 2
    What happens if you just `System.out.println(getAmount())`? – luk2302 Jun 08 '21 at 08:49
  • 2
    You may need to choose a different `MathContext` given the large number you're dealing with. Have a look at this answer here: https://stackoverflow.com/a/12395405/3415090 – mohammedkhan Jun 08 '21 at 08:51

1 Answers1

7

In my example, getAmount() returns 123456789123123424113.31.

No, it does not. That is not a value that a double can represent exactly.

You can easily verify that with this code:

double d = 123456789123123424113.31d;
System.out.println(d);

Which outputs

1.2345678912312343E20

This value has the minimum amount of digits to uniquely distinguish it from any other double value. Meaning that there aren't any more relevant digits in that double. You've already lost the precision before converting the value to BigDecimal.

While an integer data type such as long and int can exactly represent every (integer) value within its range, the same can't be said about floating point numbers: they have an immense range of values that they can represent, but at the cost of not being able to represent every possible value within the range. Effectively there's a limited number of digits that a floating point number can represent (about 16 decimal digits for double and about 7 decimal digits for float). Everything else will be cut off.

If you need arbitrary precision then something like BigDecimal can help: it will allocate as much memory as necessary to hold all digits (or round according to your specification, if required), making it much more complex but also more powerful.

BigDecimal bd = new BigDecimal("123456789123123424113.31");
System.out.println(bd);

will print

123456789123123424113.31

Make sure not to initialize the BigDecimal from a double value, as you'll only get the cut-off value even then.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • Yes, you're right. Hmmm, so what would be the correct way to store a large decimal value without losing precision? – ptk Jun 08 '21 at 08:57
  • 1
    @p4t keep it as a `String` and convert it back and forth from / to `BigDecimal` when needed. – luk2302 Jun 08 '21 at 08:58
  • 2
    @p4t: use `BigDecimal` all throughout your calculation and never convert anything to a `double` (not even for intermediate results). Also make sure those `BigDecimal` are not **initialized** from `double` values (use the `String` constructor if you need decimal points). – Joachim Sauer Jun 08 '21 at 08:59
  • 1
    @p4t a `String` as return value from `getAmount()` along with the parameterized constructor of `BigDecimal` could work... Try to sys-out the `doubleValue()` of `BigDecimal bd = new BigDecimal("123456789123123424113.31");` – deHaar Jun 08 '21 at 09:00