1

Numbers sometimes cannot be expressed exactly when they are represented in double precision or single precision. Of course working with bigdecimal is a solution, I know that.

Let's come to my question:

The number 0.20000000000000029 in double precision 0011111111001001100110011001100110011001100110011001100110100100
This is actually the number 0.20000000000000028865798640254070051014423370361328125

Similarly, the number 0.3213213214213222219 in double precision 0011111111010100100100001000011101001101110000001100010111111001 This number is actually 0.321321321421322247946505967775010503828525543212890625

Now let's look at the java code and outputs below.

class Main { public static void main(String[] args) { double x = 0.2000000000000002900; // double y = 0.3213213214213222219; // System.out.printf("x = %.20f , y = %.20f%n", x, y); } } 

Outputs: x = 0.20000000000000030000 , y = 0.32132132142132225000

From here we understand that the output number x = 0.20000000000000030000 is the number 0.20000000000000028865798640254070051014423370361328125 rounded to 16 digits.

the output number y = 0.32132132142132225000 is the number 0.321321321421322247946505967775010503828525543212890625 rounded to 17 digits.

Here is the question: Why was one of the numbers rounded to 16 precision while the other was rounded to 17? Why aren't they both 16 precision? Or why aren't they both 17 precision?

why the precisions are different?

8
  • I think this answers the "logic" behind Java's floating point output: stackoverflow.com/a/76612621/6870253 (I have no idea why your question got downvoted ..) Commented Jan 14 at 15:04
  • 2
    Doubles aren't rounded to 16 or 17 decimal digits, they're rounded to 52 binary digits, which is not always the same number of decimal digits. Commented Jan 14 at 15:18
  • 3
    @LouisWasserman: A number is indeed rounded to 53 bits (not 52) when it is converted to double, but OP is not asking about that. They are asking about the rounding to decimal when it is formatted for output. Commented Jan 14 at 15:22
  • Whether you count the 1. at the beginning is ambiguous, but more to the point, binary fractions not corresponding to a precise number of decimal digits is the answer to that question. Commented Jan 14 at 15:24
  • 3
    @LouisWasserman: Again, that is not the question. OP has the exact values of the double and is not asking about those values; they are asking about the conversion to decimal performed in the course of System.out.printf. Commented Jan 14 at 15:26

2 Answers 2

6

This is essentially answered in this answer, but I will reprise it here since the question there asks about differences between C and Java.

Conclusion

The Java specification requires a troublesome double rounding in this situation. I will detail the steps below, but the effects in these cases are:

For the first case:

  • Step 1: 0.200000000000000028865798640254070051014423370361328125 is converted to 0.2000000000000003 (16 significant digits)
  • Step 2: 0.2000000000000003 is formatted as “0.20000000000000030000”.

For the second case:

  • Step 1: 0.321321321421322247946505967775010503828525543212890625 is converted to 0.32132132142132225 (17 significant digits).
  • Step 2: 0.32132132142132225 is formatted as “0.32132132142132225000”.

What Is Step 1?

Step 1 is an attempt to find the shortest decimal numeral that can “represent” the double in some sense. Specifically, step 1 is to find the fewest number of decimal digits such that converting the double to decimal with that many digits and then converting back to double yields the original value.

In the first case, the 16 digits of 0.2000000000000003 are enough, because converting that to double produces 0.200000000000000028865798640254070051014423370361328125.

However, with the second case, if we had the 16-digit number 0.3213213214213222, converting it to double would produce 0.321321321421322192435354736517183482646942138671875, which is different from the original number. So 16 digits is not enough; we need 17 in this case.

Why Does Java Use This Two-Step Method?

This is specified in the Java documentation.

The documentation for formatting with the Double type and f format says:

… If the precision is less than the number of digits which would appear after the decimal point in the string returned by Float.toString(float) or Double.toString(double) respectively, then the value will be rounded using the round half up algorithm. Otherwise, zeros may be appended to reach the precision…

Let’s consider “the string returned by … Double.toString(double)”. For the number 0.20000000000000028865798640254070051014423370361328125, this string is “0.2000000000000003”. This is because the Java specification says that toString produces just enough decimal digits to uniquely distinguish the number within the set of Double values, and “0.2000000000000003” has just enough digits in this case.

The passage quoted above refers to rounding “the value” or appending zeros. Which value does it mean—the actual operand of format, which is 0.20000000000000028865798640254070051014423370361328125, or that string it mentions, “0.2000000000000003”? Since the latter is not a numeric value (it is a character string), I would have expected “the value” to mean the former. However, the second sentence says “Otherwise [that is, if more digits are requested], zeros may be appended…” If we were using the actual operand of format, we would show its digits, not use zeros. But, if we take the string as a numeric value, its decimal representation would have only zeros after the digits shown in it. So it seems this is the interpretation intended, and Java implementations appear to conform to that.

So, to format this number with ".20f", we first convert it to 0.2000000000000003 and then append zeros, yielding “0.20000000000000030000”.

This is a bad specification because:

  • It requires two roundings in many cases, which can increase the error. (Note: In this case, .20f resulted in appending zeros, not rounding. But a shorter precision request, like .8f, would perform a second rounding.)
  • The roundings occur in hard-to-predict and hard-to-control places. Some values will be rounded after two decimal places. Some will be rounded after 13. A program cannot easily predict this or adjust for it.

(Also, it is a shame they wrote zeros “may be” appended. Why not “Otherwise, zeros are appended to reach the precision”? With “may”, it seems like they are giving the implementation a choice, although I suspect they meant the “may” is predicated on whether zeros are needed to reach the precision, not on whether the implementor chooses to append them.)

Sign up to request clarification or add additional context in comments.

1 Comment

eric, thank's a alot, very helpful. I write an answer, can you check it?
0

Eric, Thank's a a lot. What I understand from all this (I will explain with an example):

The number 0.20000000000000029 in double precision 0011111111001001100110011001100110011001100110011001100110100100 This is actually the number 0.20000000000000028865798640254070051014423370361328125

In Java( as you explained), this number is 0.2000000000000003. This number is enough to represent. Already in double precision, the number 0.2000000000000003 and the number 0.20000000000000028865798640254070051014423370361328125 is the same(0011111111001001100110011001100110011001100110011001100110100100).

Thus, %.20f will add zero to the number 0.2000000000000003 and print 0.20000000000000030000.

If it were %.15f, it would round the number 0.2000000000000003 to 15 precision and write 0.200000000000000 on the screen.

If it were %.16f, it would round the number 0.2000000000000003 to 16 precision and write 0.2000000000000003 on the screen. Right?

5 Comments

This is not really an answer but a comment to another answer.
“This is actually the number…” is better as “This string of bits represents the number…” or “The binary64 interpretation of this string of bits is the number…” That is because the string of bits are not the number; they are merely bits we use to represent the number.
“This number is enough to represent” is better as “These digits are enough to uniquely identify the represented number.”
“the number 0.2000000000000003 and the number 0.20000000000000028865798640254070051014423370361328125 is the same” is false. Those numbers are quite obviously not the same. What is true is that, when those numbers are converted to binary64 with round-to-nearest-ties-to-even, they produce the same result.
@EricPostpischil Okey, I understand everythink! thank's for all Eric. by the way, what do you think of about "If it were %.15f, it would round the number 0.2000000000000003 to 15 precision and write 0.200000000000000 on the screen. If it were %.16f, it would round the number 0.2000000000000003 to 16 precision and write 0.2000000000000003 on the screen. Right?"

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.