2

The second of below method calls, to setYCoordinate(), gets incorrect value -89.99999435599995 instead of -89.99999435599994.

The first call to setXCoordinate() gets correct value 29.99993874900002.

setXCoordinate(BigDecimal.valueOf(29.99993874900002))
setYCoordinate(BigDecimal.valueOf(-89.99999435599994))

I put a breakpoint inside BigDecimal.valueOf() - this method's code looks as below -

public static BigDecimal valueOf(double val) {
        // Reminder: a zero double returns '0.0', so we cannot fastpath
        // to use the constant ZERO.  This might be important enough to
        // justify a factory approach, a cache, or a few private
        // constants, later.
        return new BigDecimal(Double.toString(val));
    }

The argument received by valueOf i.e. "double val" itself is -89.99999435599995 when inspected. Why? I have Java version set as below in my Maven pom.xml

<java.version>1.8</java.version>
jerry.pepper
  • 165
  • 1
  • 12
  • Java has apparently [15 decimal digits of precision](https://stackoverflow.com/a/36345535/1270789), but your coordinates have 16 digits. You shouldn't be relying on that degree of accuracy anyway. – Ken Y-N Jul 16 '18 at 23:04
  • 2
    @KenY-N That's not what it says in your citation. It has 15.9 *integral* decimal digits of precision. What that translates to after the decimal point is value-dependent. See the last sentence of your citation. Which I wrote. – user207421 Jul 16 '18 at 23:09
  • Closely related: https://stackoverflow.com/questions/16216248/convert-java-number-to-bigdecimal-best-way – Hulk Jul 17 '18 at 06:33
  • ...but the conversion that causes the confusion in this question happens *before* any method is invoked, when the literal is interpreted as `double`. – Hulk Jul 17 '18 at 06:43

3 Answers3

9

Because a double can't retain that much precision; you shouldn't use a double, but rather a String when initializing your BigDecimal:

new BigDecimal("29.99993874900002");
new BigDecimal("-89.99999435599994");

See: Is floating point math broken?

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • After changing to setYCoordinate(new BigDecimal(-89.99999435599994)) the value received by setYCoordinate is -89.99999435599994512813282199203968048095703125 - I do not need and want that many digits as I am doing an exact comparison elsewhere. – jerry.pepper Jul 16 '18 at 23:21
  • 2
    @jerry.pepper You should use a `String` as shown in my answer. – Jacob G. Jul 16 '18 at 23:22
  • @jerry.pepper you can either round the result is use the String representation to get more precision than double supports. – Peter Lawrey Jul 17 '18 at 05:59
1

Your confusion has nothing to do with BigDecimal.

double d = -89.99999435599994;
System.out.println(d); //or inspecting it in a debugger

yields:

-89.99999435599995

This is just the way doubles work in java, in combination with the way Double.toString defines the String-representation. This conversion happens before any method is invoked, when the literal is interpreted as double. The details are specified in JLS Chapter 3.10.2. Floating-Point Literals and the JavaDocs of Double.valueOf(String).


If you need to express the value -89.99999435599994 as BigDecimal, the easiest way is to use the constructor taking a String, as other answers have already pointed out:

BigDecimal bd = new BigDecimal("-89.99999435599994");

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

yields:

-89.99999435599994

Hulk
  • 6,399
  • 1
  • 30
  • 52
0

You're right on the edge of precision for a double-precision floating point value, with 16 digits specified, and there's just shy of a full 16 digits of decimal accuracy available. If you skip BigDecimal entirely, just set a double to -89.99999435599994 and print it back out, you'll get -89.99999435599995.

kshetline
  • 12,547
  • 4
  • 37
  • 73