I am unable to understand how Double.toString() works in Java/JVM. My understanding is that in general fraction numbers cannot be represented precisely in floating point typ
The number of digits you see when a float
or a double
is printed is a consequence of Java’s rules for default conversion of float
and double
to decimal.
Java’s default formatting for floating-point numbers uses the fewest significant decimal digits needed to distinguish the number from nearby representable numbers.1
In your example, 206.64
in source text is converted to the double
value 206.6399999999999863575794734060764312744140625, because, of all the values representable in the double
type, that one is closest to 206.64. The next lower and next higher values are 206.639999999999957935870043002068996429443359375 and
206.640000000000014779288903810083866119384765625.
When printing this value, Java only needs to print “206.64”, because that is enough that we can pick out the double
value 206.6399999999999863575794734060764312744140625 from its neighbors 206.639999999999957935870043002068996429443359375 and
206.640000000000014779288903810083866119384765625. Note that, starting from the end of the 9s in 206.63999…, that first value differs from 206.64 by .1364…, whereas the third value, 206.64000…, differs by .1477…. So, when Java prints “206.64”, it means the value of the double
being printed is the nearest representable value, and that is the 206.6399999999999863575794734060764312744140625 value, not the farther 206.640000000000014779288903810083866119384765625 value.
1 The rule for Java SE 10 can be found in the documentation for java.lang.float, in the toString(float d)
section. The double
documentation is similar. The passage, with the most relevant part in bold, is:
Returns a string representation of the
float argument
. All characters mentioned below are ASCII characters.
If the argument is NaN, the result is the string "NaN".
Otherwise, the result is a string that represents the sign and magnitude (absolute value) of the argument. If the sign is negative, the first character of the result is '
-
' ('\u002D'
); if the sign is positive, no sign character appears in the result. As for the magnitude m:
If m is infinity, it is represented by the characters "Infinity"; thus, positive infinity produces the result "Infinity" and negative infinity produces the result "-Infinity".
If m is zero, it is represented by the characters "0.0"; thus, negative zero produces the result "-0.0" and positive zero produces the result "0.0".
If m is greater than or equal to 10-3 but less than 107, then it is represented as the integer part of m, in decimal form with no leading zeroes, followed by '
.
' ('\u002E'
), followed by one or more decimal digits representing the fractional part of m.If m is less than 10-3 or greater than or equal to 107, then it is represented in so-called "computerized scientific notation." Let n be the unique integer such that 10n ≤ m < 10n+1; then let a be the mathematically exact quotient of m and 10n so that 1 ≤ a < 10. The magnitude is then represented as the integer part of a, as a single decimal digit, followed by '
.
' ('\u002E'
), followed by decimal digits representing the fractional part of a, followed by the letter 'E
' ('\u0045'
), followed by a representation of n as a decimal integer, as produced by the methodInteger.toString(int)
.How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type
float
. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument f. Then f must be thefloat
value nearest to x; or, if twofloat
values are equally close to x, then f must be one of them and the least significant bit of the significand of f must be 0.
I am somewhat novice, so I hope someone with more experience can answer more thoroughly, but here is what I theorize is the reason...
Although this is for the .NET framework and not specifically Java, I imagine that they work similarly: the toString method uses an optional formatter input, and most likely Java uses something similar, formatting the double to a close approximation in the toString method. Considering that Oracle specifically states that toString should be concise and easy-to-read, likely such a method is implemented for Double.toString().
This is about as much documentation as I could find on the specifics of the Double.toString() method -- note the last paragraph:
How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.
I am curious what it means by "adjacent values of type double" (other variables?), but it seems to also concur with the above -- toString and other methods likely only use as few digits as possible to uniquely identify the double, rounding when the number is arbitrarily close enough, as in the case of 23.675999999999 being "close enough" to 23.676. Or I could be wildly misunderstanding the documentation.