5

IEEE 754 float numbers are discret.

public class MyTest2 {
  public static void main(String[] args) {
    //about 1.00000001490116119384765625E-1 in IEEE-754
    float f = 0.1f;
    //about 1.00000000000000005551115123126E-1 in IEEE-754
    double d = 0.1d;
    System.out.println(String.format("double 0.1= %.30f", d));
    System.out.println(String.format("float 0.1 = %.15f", f));
    System.out.println(d+"");
  }
}

See this code run live at IdeOne.com. Running in JDK8, output is

double 0.1= 0.100000000000000000000000000000
float 0.1 = 0.100000001490116
0.1

The float value is printed as expected. I expect double value 0.1d to be printed something like 1.000000000000000055511151231260. Why it print all zeros in fraction part?

If I convert double variable d to string, it prints 0.1.

System.out.println(d+"");

How does java convet the neartest float value of 0.1d (which is stroed as about 1.00000001490116119384765625E-1) to exact 0.1?

Chen Li
  • 320
  • 1
  • 10
  • 1
    The float has a precision of only six to seven digits. There is no point in pursuing an absolutely accurate float. Do you have any special needs that you have to do? – HUTUTU Feb 09 '22 at 07:06
  • No special needs, just to verify my understanding about the concept. – Chen Li Feb 09 '22 at 07:38
  • Yes, that `double` has a value of 0.1000000000000000055511151231257827021181583404541015625. – Ole V.V. Feb 09 '22 at 07:41
  • There are some similar bugs, see the list under https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7131459. I didn’t readily find this particular one. – Ole V.V. Feb 09 '22 at 07:48
  • I tend to think of it as a bug, though. [The docs](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#syntax) say: *The result is a string that represents the sign and magnitude (absolute value) of the argument.* If I use `%57.55f`, it should exactly fit the precise value of `0.1000000000000000055511151231257827021181583404541015625`, but I get `0.1000000000000000000000000000000000000000000000000000000`. – Ole V.V. Feb 09 '22 at 08:01
  • Question remains: I expect double value 0.1d to be printed something like 1.000000000000000055511151231260. Why it print all zeros in fraction part? – Chen Li Feb 09 '22 at 11:50
  • 1
    @HUTUTU: [`float` does not have “a precision of only six to seven digits.”](https://stackoverflow.com/questions/61609276/how-to-calculate-float-type-precision-and-does-it-make-sense/61614323#61614323) The behavior of IEEE-754 binary32 and binary64 arithmetic is well specified, and we can expect specific results from it. Those results generally differ from real-number arithmetic but nonetheless can be used productively when the arithmetic is understood and properly implemented. – Eric Postpischil Feb 09 '22 at 12:34
  • Does this answer your question? [float data type precision in Java](https://stackoverflow.com/questions/55108735/float-data-type-precision-in-java) – pringi Feb 28 '22 at 14:08

2 Answers2

5

The Java specification requires this imperfect display of values. The f format produces only as many significant digits as the Double.toString(double) method would produce and then mindlessy appends zeros to get to the requested precision.

Per the documentation, for the f format, if the precision exceeds the number of digits after the decimal point that Double.toString(double) would produce, then “zeros may be appended to reach the precision.” This does not state what those zeros are appended to. Presumably, they are appended to the string that Double.toString(double) would produce.

The documentation for Double.toString(double) says it produces “as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double.” I discuss that further here. For 0.1000000000000000055511151231257827021181583404541015625, Double.toString(double) produces “0.1”. (The neighboring values, 0.09999999999999999167332731531132594682276248931884765625 and 0.10000000000000001942890293094023945741355419158935546875, are both further from .1 than 0.1000000000000000055511151231257827021181583404541015625 is, and they are formatted as “0.09999999999999999” and “0.10000000000000002”, so “0.1” serves to uniquely distinguish 0.1000000000000000055511151231257827021181583404541015625 from its neighbors.)

Thus, System.out.println(String.format("double 0.1= %.30f", d)) starts with the “0.1” from Double.toString(double) and appends 29 zeroes.

Similarly, if you change d to 0.09999999999999999167332731531132594682276248931884765625, String.format produces “0.099999999999999990000000000000”—it has taken the toString result and appended zeros. And for 0.10000000000000001942890293094023945741355419158935546875 it produces “0.100000000000000020000000000000”.

This conforms to the specification. The specified behavior is incapable of presenting the true value correctly, so I regard the specification as defective.

Incidentally, the Java specification is troublesome whether the requested precision is greater than or less than the number of digits that Double.toString(double) would produce. In the case when the request permission is less, the Java specification requires a double rounding that can increase errors.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    In contrast the [`BigDecimal(double)` ctor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigDecimal.html#%3Cinit%3E(double)) **_does_ provide the exact value** (of the binary approximation) -- and is described as 'unpredictable' (which isn't really true, but 'surprising to many' is). – dave_thompson_085 Feb 09 '22 at 13:33
  • Thanks for your explain, accurate answer. The Float.toString specification aligns to intuition but is an illusion that double can represent the exact math value in case of 0.1d. I'm curious what tool you use to get the exact values of 0.1d and neighbor values. – Chen Li Feb 09 '22 at 14:01
  • @ChenLi: I use Apple’s standard library on macOS, with C programs. For decimal-to-binary and binary-to-decimal conversions in `printf` and such, it provides correct rounding for any number of digits. When I need to go beyond what `double` arithmetic provides easily, I use an old version of Maple I have or Wolfram Alpha on the web, but those were not needed in this case. – Eric Postpischil Feb 09 '22 at 19:33
0

If you see java ource code, you observe that

System.out.println(d+"");

Will use something like this:

Double.toString(d)

that in turn will call

FloatingDecimal.toJavaFormatString(d)

String.format uses other mechanism. That is why you see this behaviour.

References:

pringi
  • 3,987
  • 5
  • 35
  • 45
  • Thanks for you post. This explains how Java concatenate double to string. Another question remains: I expect double value 0.1d to be printed something like 1.000000000000000055511151231260. Why it print all zeros in fraction part? – Chen Li Feb 09 '22 at 11:47