JDK-7021568 : Double.parseDouble() returns architecture dependent results
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6u24
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2011-02-22
  • Updated: 2012-03-20
  • Resolved: 2011-05-17
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b140Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

EXTRA RELEVANT SYSTEM CONFIGURATION :
This also occurs under OpenJDK on Linux:

java version "1.6.0_20"
OpenJDK Runtime Environment (IcedTea6 1.9.4) (6b20-1.9.4-0ubuntu1)
OpenJDK Client VM (build 19.0-b09, mixed mode, sharing)

A DESCRIPTION OF THE PROBLEM :
The result of the conversion of the decimal string representing the value 2^-1047 + 2^-1075 depends on the architecture on which Java runs. For example, using x87 instructions, the converted result is 0x0.0000008p-1022; using SSE instructions, the converted result is 0x0.0000008000001p-1022.

I believe the fix is to add the 'strictfp' keyword to the FloatingDecimal class, either at the class or doubleValue() method level (I tried both -- both work). When that is done, the result is consistently 0x0.0000008000001p-1022 (which is incorrect by one ULP, but that I'll report as a separate bug).

(See my article http://www.exploringbinary.com/nondeterministic-floating-point-conversions-in-java/ for more details, including examples on 64-bit systems and on the Power architecture.)
 

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached testcase diffconvert.java twice:

1) java -XX:UseSSE=0 diffconvert
(no output)

2) java -XX:UseSSE=2 diffconvert
Iteration 113 converts as 0x0.0000008p-1022
Iteration 114 converts as 0x0.0000008000001p-1022

In this case, the switchover from default x87 FPU instructions to JIT produced SSE instructions causes the result to change. (The iteration in which it switches varies from run to run).


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The same converted value no matter which floating-point instructions are used to compute it.
ACTUAL -
Two different converted values for the same decimal string.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
class diffconvert {
 public static void main(String[] args) {
   double conversion, conversion_prev = 0;
   //2^-1047 + 2^-1075
   String decimal = "6.631236871469758276785396630275967243399099947355303144249971758736286630139265439618068200788048744105960420552601852889715006376325666595539603330361800519107591783233358492337208057849499360899425128640718856616503093444922854759159988160304439909868291973931426625698663157749836252274523485312442358651207051292453083278116143932569727918709786004497872322193856150225415211997283078496319412124640111777216148110752815101775295719811974338451936095907419622417538473679495148632480391435931767981122396703443803335529756003353209830071832230689201383015598792184172909927924176339315507402234836120730914783168400715462440053817592702766213559042115986763819482654128770595766806872783349146967171293949598850675682115696218943412532098591327667236328125E-316";

   for(int i = 1; i <= 10000; i++) {
     conversion = Double.parseDouble(decimal);
     if (i != 1 && conversion != conversion_prev) {
        System.out.printf("Iteration %d converts as %a%n",
                          i-1,conversion_prev);
        System.out.printf("Iteration %d converts as %a%n",
                          i,conversion);
     }
     conversion_prev = conversion;
   }
 }
}
---------- END SOURCE ----------

Comments
PUBLIC COMMENTS See http://hg.openjdk.java.net/jdk7/tl/jdk/rev/bcc6e4c28684
18-04-2011

SUGGESTED FIX # HG changeset patch # User darcy # Date 1303105946 25200 # Node ID bcc6e4c286848b9cc2138f6ecdff8ee9e649e6c6 # Parent caebdaf362ee0e464a6dbbea752bba962965e5ae 7021568: Double.parseDouble() returns architecture dependent results Reviewed-by: alanb --- a/src/share/classes/sun/misc/FloatingDecimal.java Sun Apr 17 16:19:29 2011 -0700 +++ b/src/share/classes/sun/misc/FloatingDecimal.java Sun Apr 17 22:52:26 2011 -0700 @@ -30,7 +30,7 @@ import sun.misc.FloatConsts; import sun.misc.FloatConsts; import java.util.regex.*; -public class FloatingDecimal{ +public strictfp class FloatingDecimal{ boolean isExceptional; boolean isNegative; int decExponent; --- a/src/share/classes/sun/misc/FormattedFloatingDecimal.java Sun Apr 17 16:19:29 2011 -0700 +++ b/src/share/classes/sun/misc/FormattedFloatingDecimal.java Sun Apr 17 22:52:26 2011 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,7 @@ import sun.misc.FloatConsts; import sun.misc.FloatConsts; import java.util.regex.*; -public class FormattedFloatingDecimal{ +public strictfp class FormattedFloatingDecimal{ boolean isExceptional; boolean isNegative; int decExponent; // value set at construction, then immutable --- a/test/java/lang/Double/ParseDouble.java Sun Apr 17 16:19:29 2011 -0700 +++ b/test/java/lang/Double/ParseDouble.java Sun Apr 17 22:52:26 2011 -0700 @@ -23,7 +23,7 @@ /* * @test - * @bug 4160406 4705734 4707389 4826774 4895911 4421494 + * @bug 4160406 4705734 4707389 4826774 4895911 4421494 7021568 * @summary Test for Double.parseDouble method and acceptance regex */ @@ -581,6 +581,31 @@ public class ParseDouble { } } + + private static void testStrictness() { + final double expected = 0x0.0000008000001p-1022; + boolean failed = false; + double conversion = 0.0; + double sum = 0.0; // Prevent conversion from being optimized away + + //2^-1047 + 2^-1075 + String decimal = "6.631236871469758276785396630275967243399099947355303144249971758736286630139265439618068200788048744105960420552601852889715006376325666595539603330361800519107591783233358492337208057849499360899425128640718856616503093444922854759159988160304439909868291973931426625698663157749836252274523485312442358651207051292453083278116143932569727918709786004497872322193856150225415211997283078496319412124640111777216148110752815101775295719811974338451936095907419622417538473679495148632480391435931767981122396703443803335529756003353209830071832230689201383015598792184172909927924176339315507402234836120730914783168400715462440053817592702766213559042115986763819482654128770595766806872783349146967171293949598850675682115696218943412532098591327667236328125E-316"; + + for(int i = 0; i <= 12_000; i++) { + conversion = Double.parseDouble(decimal); + sum += conversion; + if (conversion != expected) { + failed = true; + System.out.printf("Iteration %d converts as %a%n", + i, conversion); + } + } + + System.out.println("Sum = " + sum); + if (failed) + throw new RuntimeException("Inconsistent conversion"); + } + public static void main(String[] args) throws Exception { rudimentaryTest(); @@ -595,5 +620,6 @@ public class ParseDouble { testRegex(paddedBadStrings, true); testSubnormalPowers(); + testStrictness(); } }
18-04-2011

EVALUATION Yes.
16-04-2011