Skip to content

Commit 8ff7d6e

Browse files
committed
8295391: Add discussion of binary <-> decimal conversion issues
Reviewed-by: bpb
1 parent 99570fb commit 8ff7d6e

File tree

2 files changed

+157
-3
lines changed

2 files changed

+157
-3
lines changed

src/java.base/share/classes/java/lang/Double.java

+146
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,150 @@
200200
* floating-point values, the three relations only differ if at least
201201
* one argument is zero or NaN.
202202
*
203+
* <h2><a id=decimalToBinaryConversion>Decimal &harr; Binary Conversion Issues</a></h2>
204+
*
205+
* Many surprising results of binary floating-point arithmetic trace
206+
* back to aspects of decimal to binary conversion and binary to
207+
* decimal conversion. While integer values can be exactly represented
208+
* in any base, which fractional values can be exactly represented in
209+
* a base is a function of the base. For example, in base 10, 1/3 is a
210+
* repeating fraction (0.33333....); but in base 3, 1/3 is exactly
211+
* 0.1<sub>(3)</sub>, that is 1&nbsp;&times;&nbsp;3<sup>-1</sup>.
212+
* Similarly, in base 10, 1/10 is exactly representable as 0.1
213+
* (1&nbsp;&times;&nbsp;10<sup>-1</sup>), but in base 2, it is a
214+
* repeating fraction (0.0001100110011...<sub>(2)</sub>).
215+
*
216+
* <p>Values of the {@code float} type have {@value Float#PRECISION}
217+
* bits of precision and values of the {@code double} type have
218+
* {@value Double#PRECISION} bits of precision. Therefore, since 0.1
219+
* is a repeating fraction in base 2 with a four-bit repeat, {@code
220+
* 0.1f} != {@code 0.1d}. In more detail, including hexadecimal
221+
* floating-point literals:
222+
*
223+
* <ul>
224+
* <li>The exact numerical value of {@code 0.1f} ({@code 0x1.99999a0000000p-4f}) is
225+
* 0.100000001490116119384765625.
226+
* <li>The exact numerical value of {@code 0.1d} ({@code 0x1.999999999999ap-4d}) is
227+
* 0.1000000000000000055511151231257827021181583404541015625.
228+
* </ul>
229+
*
230+
* These are the closest {@code float} and {@code double} values,
231+
* respectively, to the numerical value of 0.1. These results are
232+
* consistent with a {@code float} value having the equivalent of 6 to
233+
* 9 digits of decimal precision and a {@code double} value having the
234+
* equivalent of 15 to 17 digits of decimal precision. (The
235+
* equivalent precision varies according to the different relative
236+
* densities of binary and decimal values at different points along the
237+
* real number line).
238+
*
239+
* <p>This representation hazard of decimal fractions is one reason to
240+
* use caution when storing monetary values as {@code float} or {@code
241+
* double}. Alternatives include:
242+
* <ul>
243+
* <li>using {@link java.math.BigDecimal BigDecimal} to store decimal
244+
* fractional values exactly
245+
*
246+
* <li>scaling up so the monetary value is an integer &mdash; for
247+
* example, multiplying by 100 if the value is denominated in cents or
248+
* multiplying by 1000 if the value is denominated in mills &mdash;
249+
* and then storing that scaled value in an integer type
250+
*
251+
*</ul>
252+
*
253+
* <p>For each finite floating-point value and a given floating-point
254+
* type, there is a contiguous region of the real number line which
255+
* maps to that value. Under the default round to nearest rounding
256+
* policy (JLS {@jls 15.4}), this contiguous region for a value is
257+
* typically one {@linkplain Math#ulp ulp} (unit in the last place)
258+
* wide and centered around the exactly representable value. (At
259+
* exponent boundaries, the region is asymmetrical and larger on the
260+
* side with the larger exponent.) For example, for {@code 0.1f}, the
261+
* region can be computed as follows:
262+
*
263+
* <br>// Numeric values listed are exact values
264+
* <br>oneTenthApproxAsFloat = 0.100000001490116119384765625;
265+
* <br>ulpOfoneTenthApproxAsFloat = Math.ulp(0.1f) = 7.450580596923828125E-9;
266+
* <br>// Numeric range that is converted to the float closest to 0.1, _excludes_ endpoints
267+
* <br>(oneTenthApproxAsFloat - &frac12;ulpOfoneTenthApproxAsFloat, oneTenthApproxAsFloat + &frac12;ulpOfoneTenthApproxAsFloat) =
268+
* <br>(0.0999999977648258209228515625, 0.1000000052154064178466796875)
269+
*
270+
* <p>In particular, a correctly rounded decimal to binary conversion
271+
* of any string representing a number in this range, say by {@link
272+
* Float#parseFloat(String)}, will be converted to the same value:
273+
*
274+
* {@snippet lang="java" :
275+
* Float.parseFloat("0.0999999977648258209228515625000001"); // rounds up to oneTenthApproxAsFloat
276+
* Float.parseFloat("0.099999998"); // rounds up to oneTenthApproxAsFloat
277+
* Float.parseFloat("0.1"); // rounds up to oneTenthApproxAsFloat
278+
* Float.parseFloat("0.100000001490116119384765625"); // exact conversion
279+
* Float.parseFloat("0.100000005215406417846679687"); // rounds down to oneTenthApproxAsFloat
280+
* Float.parseFloat("0.100000005215406417846679687499999"); // rounds down to oneTenthApproxAsFloat
281+
* }
282+
*
283+
* <p>Similarly, an analogous range can be constructed for the {@code
284+
* double} type based on the exact value of {@code double}
285+
* approximation to {@code 0.1d} and the numerical value of {@code
286+
* Math.ulp(0.1d)} and likewise for other particular numerical values
287+
* in the {@code float} and {@code double} types.
288+
*
289+
* <p>As seen in the above conversions, compared to the exact
290+
* numerical value the operation would have without rounding, the same
291+
* floating-point value as a result can be:
292+
* <ul>
293+
* <li>greater than the exact result
294+
* <li>equal to the exact result
295+
* <li>less than the exact result
296+
* </ul>
297+
*
298+
* A floating-point value doesn't "know" whether it was the result of
299+
* rounding up, or rounding down, or an exact operation; it contains
300+
* no history of how it was computed. Consequently, the sum of
301+
* {@snippet lang="java" :
302+
* 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f;
303+
* // Numerical value of computed sum: 1.00000011920928955078125,
304+
* // the next floating-point value larger than 1.0f, equal to Math.nextUp(1.0f).
305+
* }
306+
* or
307+
* {@snippet lang="java" :
308+
* 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d + 0.1d;
309+
* // Numerical value of computed sum: 0.99999999999999988897769753748434595763683319091796875,
310+
* // the next floating-point value smaller than 1.0d, equal to Math.nextDown(1.0d).
311+
* }
312+
*
313+
* should <em>not</em> be expected to be exactly equal to 1.0, but
314+
* only to be close to 1.0. Consequently, the following code is an
315+
* infinite loop:
316+
*
317+
* {@snippet lang="java" :
318+
* double d = 0.0;
319+
* while(d != 1.0) { // Surprising infinite loop
320+
* d += 0.1; // Sum never _exactly_ equals 1.0
321+
* }
322+
* }
323+
*
324+
* Instead, use an integer loop count for counted loops:
325+
*
326+
* {@snippet lang="java" :
327+
* double d = 0.0;
328+
* for(int i = 0; i < 10; i++) {
329+
* d += 0.1;
330+
* } // Value of d is equal to Math.nextDown(1.0).
331+
* }
332+
*
333+
* or test against a floating-point limit using ordered comparisons
334+
* ({@code <}, {@code <=}, {@code >}, {@code >=}):
335+
*
336+
* {@snippet lang="java" :
337+
* double d = 0.0;
338+
* while(d <= 1.0) {
339+
* d += 0.1;
340+
* } // Value of d approximately 1.0999999999999999
341+
* }
342+
*
343+
* While floating-point arithmetic may have surprising results, IEEE
344+
* 754 floating-point arithmetic follows a principled design and its
345+
* behavior is predictable on the Java platform.
346+
*
203347
* @jls 4.2.3 Floating-Point Types, Formats, and Values
204348
* @jls 4.2.4. Floating-Point Operations
205349
* @jls 15.21.1 Numerical Equality Operators == and !=
@@ -750,6 +894,7 @@ public static String toHexString(double d) {
750894
* represented by the {@code String} argument.
751895
* @throws NumberFormatException if the string does not contain a
752896
* parsable number.
897+
* @see Double##decimalToBinaryConversion Decimal &harr; Binary Conversion Issues
753898
*/
754899
public static Double valueOf(String s) throws NumberFormatException {
755900
return new Double(parseDouble(s));
@@ -786,6 +931,7 @@ public static Double valueOf(double d) {
786931
* @throws NumberFormatException if the string does not contain
787932
* a parsable {@code double}.
788933
* @see java.lang.Double#valueOf(String)
934+
* @see Double##decimalToBinaryConversion Decimal &harr; Binary Conversion Issues
789935
* @since 1.2
790936
*/
791937
public static double parseDouble(String s) throws NumberFormatException {

src/java.base/share/classes/java/lang/Float.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,17 @@
5656
* <h2><a id=equivalenceRelation>Floating-point Equality, Equivalence,
5757
* and Comparison</a></h2>
5858
*
59-
* The class {@code java.lang.Double} has a <a
60-
* href="Double.html#equivalenceRelation">discussion of equality,
61-
* equivalence, and comparison of floating-point values</a> that is
59+
* The class {@code java.lang.Double} has a {@linkplain
60+
* Double##equivalenceRelation discussion of equality,
61+
* equivalence, and comparison of floating-point values} that is
6262
* equally applicable to {@code float} values.
6363
*
64+
* <h2><a id=decimalToBinaryConversion>Decimal &harr; Binary Conversion Issues</a></h2>
65+
*
66+
* The {@linkplain Double##decimalToBinaryConversion discussion of binary to
67+
* decimal conversion issues} in {@code java.lang.Double} is also
68+
* applicable to {@code float} values.
69+
*
6470
* @see <a href="https://standards.ieee.org/ieee/754/6210/">
6571
* <cite>IEEE Standard for Floating-Point Arithmetic</cite></a>
6672
*
@@ -515,6 +521,7 @@ public static String toHexString(float f) {
515521
* represented by the {@code String} argument.
516522
* @throws NumberFormatException if the string does not contain a
517523
* parsable number.
524+
* @see Double##decimalToBinaryConversion Decimal &harr; Binary Conversion Issues
518525
*/
519526
public static Float valueOf(String s) throws NumberFormatException {
520527
return new Float(parseFloat(s));
@@ -550,6 +557,7 @@ public static Float valueOf(float f) {
550557
* @throws NumberFormatException if the string does not contain a
551558
* parsable {@code float}.
552559
* @see java.lang.Float#valueOf(String)
560+
* @see Double##decimalToBinaryConversion Decimal &harr; Binary Conversion Issues
553561
* @since 1.2
554562
*/
555563
public static float parseFloat(String s) throws NumberFormatException {

0 commit comments

Comments
 (0)