-1

Can someone explain me why BigDecimal.doubleValue() gives me such results:

BigDecimal bd1 = new BigDecimal("38.399999999999999"); //15 decimals
double dd1 = bd1.doubleValue();
dd1 - > 38.4

BigDecimal bd2 = new BigDecimal("38.39999999999999"); //14 decimals
double dd2 = bd2.doubleValue();
dd2 - > 38.39999999999999
Federico klez Culloca
  • 26,308
  • 17
  • 56
  • 95
  • 2
    What did you expect as the results? – Sweeper Jun 23 '23 at 09:30
  • 2
    You are likely seeing the limit of Java's double precision with 15 decimal places. With 14 decimal places, Java can correctly store that as a double, but at 15, it does not have enough bits, so it rounds up to 38.4. Read up on how floating point numbers are stored in binary if you care to learn more. It is quite interesting. To be clear, *not all numbers with 15 decimal places cannot be represented by a Java double*, but this one cannot. – Liftoff Jun 23 '23 at 09:34
  • Does this answer your question? [Retain precision with double in Java](https://stackoverflow.com/questions/322749/retain-precision-with-double-in-java) – Laurenz Albe Jun 27 '23 at 22:10

2 Answers2

2

A double is limited to 64-bit. Using the Pigeon Hole Principle, that means there are no more than 264 unique values that a double could possibly represent.

That's a problem: doubles purport to represent any number between minus infinity and positive infinity. There are an infinite amount of numbers between 0 and 1, let alone minus infinity and +infinity, and 'infinity' is vastly larger than 264 is.

So, how does that work?

There are slightly fewer than 264 'blessed' numbers - the chosen numbers: These are exactly representable by a double. non-blessed numbers simply aren't a thing a double can represent. They aren't equally distributed; there are more blessed numbers near 0, and fewer as you get further away from it. Around 252 there are less than 1 blessed number per number (meaning, above 252, there are whole integers that are not blessed).

So how does double math work, when the result isn't blessed? Everything in double math is rounded to the nearest blessed number at every step.

This explains a few things:

double d = 0; // 0 is blessed.
for (int i = 0; i < 10; i++) {
  d += 0.1; // uhoh! 0.1, simple as it seems, is NOT blessed!
}
double f = 1.0; // 1.0 is blessed.
System.out.println(f == d); // false ????
System.out.println(d); // 0.9999999 ??

Run this code and marvel: It really does print false - evidently, ten times 0.1 isn't the same as 1.0. This is that rounding thing at work: ROUND(10 * ROUND(0.1)) isn't the same as ROUND(1.0).

In case you are wondering why something as utterly trivial as 0.1 isn't blessed: Computers count in binary. Just like 1/3 isn't blessed in our decimal system either (it's 0.3333 infinitely repeating - no finite piece of paper can properly represent it in decimal notation!), that's because '3' does not fit well into '10', '1/10th', which works fine in decimal (0.1 entirely represents it), is in binary problematic the same way 1/3 is a problem in decimal. Everything other than factors of 2 is a problem. For example, 7/64th is blessed. Because 64 is 26.

So why do you get the results you showed?

Simple: Because the BigDecimal's .doubleValue() method returns the nearest blessed number to what the BD represents. It has to - double cannot represent non-blessed numbers, but BD can, so what to do if the BD number is non-blessed and you invoke .doubleValue()? The nearest blessed number to 38.399999999999999 (which, obviously, isn't itself blessed) is X, and the nearest number to 38.39999999999999 is Y. X and Y aren't the same. System.out.println is not appropriate for ever printing doubles - you really need to tell the system how to round it (use printf("%.6d", value) instead for example), it rounds off very small errors, but that's it. That weird rounding printing stuff is written so that X is printed as 38.4 and Y is printed as 38.39999999999999.

(X and Y are actual numbers, written out in decimal they look very complicated, and I don't think it's worthwhile doing the math to figure out what they are, precisely. You can convert these doubles back to BigDecimal and print those to see the exact value - that gets rid of the rounding that System.out.println does, I think).

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
0

If you're using BigDecimal, I would recommend using the toPlainString method to return the value.

When using the doubleValue or floatValue methods, you'll lose the actual value.

BigDecimal value = new BigDecimal(String.valueOf(Math.PI));
value = value.multiply(value);
value = value.multiply(value);
value = value.multiply(value);
value = value.multiply(value);
System.out.println(value.toPlainString());

Output

90032220.842933170224774891041752473906047799260574125283393884166357894649869776701747353170025332448686774232646905942508057497083873994908066703986662914765042688909272008114678811713595048225532659579173873060345386806104478300816490312791667201

And, here is a reference.

Once the available space reserved for scale values is diminished, the value changes.

Consider the following.

String string, binary;
BigDecimal value;
for (int count = 1; count <= 20; count++) {
    string = "38.3" + "9".repeat(count);
    value = new BigDecimal(string);
    binary = Long.toBinaryString(Double.doubleToLongBits(value.doubleValue()));
    System.out.println(binary + " " + value.doubleValue());
}

Output

100000001000011001100011110101110000101000111101011100001010010 38.39
100000001000011001100110001001001101110100101111000110101010000 38.399
100000001000011001100110010111111101100010101101101010111010000 38.3999
100000001000011001100110011001011011111010100000101110100010000 38.39999
100000001000011001100110011001100101010110011111011011101100011 38.399999
100000001000011001100110011001100110010010111000111001110011110 38.3999999
100000001000011001100110011001100110011000111011011100110100100 38.39999999
100000001000011001100110011001100110011001100010000110101110010 38.399999999
100000001000011001100110011001100110011001100101111110000111001 38.3999999999
100000001000011001100110011001100110011001100110010110110110100 38.39999999999
100000001000011001100110011001100110011001100110011001010100110 38.399999999999
100000001000011001100110011001100110011001100110011001100100101 38.3999999999999
100000001000011001100110011001100110011001100110011001100110010 38.39999999999999
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4
100000001000011001100110011001100110011001100110011001100110011 38.4

If we remove some of the data, you can see the shift.

             11  11   1111   111          1111   111            38.39
             11  11  11         11 111      1111   11           38.399
             11  11  11    1111111 11       11 11     111       38.3999
             11  11  11  11    11 11111           111           38.39999
             11  11  11  11  11        11  11111 11 111 11   11 38.399999
             11  11  11  11  11  11       111   111  111  1111  38.3999999
             11  11  11  11  11  11  11   111 11 111  11        38.39999999
             11  11  11  11  11  11  11  11        11   111     38.399999999
             11  11  11  11  11  11  11  11    111111    111    38.3999999999
             11  11  11  11  11  11  11  11  11    11 11 11     38.39999999999
             11  11  11  11  11  11  11  11  11  11         11  38.399999999999
             11  11  11  11  11  11  11  11  11  11  11         38.3999999999999
             11  11  11  11  11  11  11  11  11  11  11  11     38.39999999999999
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
             11  11  11  11  11  11  11  11  11  11  11  11  11 38.4
Reilas
  • 3,297
  • 2
  • 4
  • 17