Review: 4396272 - Parsing doubles fails to follow IEEE for largest decimal that should yield 0

Brian Burkhalter brian.burkhalter at oracle.com
Fri Feb 15 01:23:46 UTC 2013


The patch below is as submitted to OpenJDK bugzilla but with enhanced comments. It pertains to the correction loop in the doubleValue() method of FloatingDecimal. The situation appears to arise when the candidate value is less than 2*Double.MIN_NORMAL as for such values the ULP is less than 2*Double.MIN_VALUE so that the intermediate result 0.5*ULP is less than Double.MIN_VALUE which rounds to zero per FP-strict and the correction is therefore zero. Thus the candidate value is unchanged. The fix is to add the ULP to twice the candidate value, obtain the intermediate result, and then halve it to obtain the adjusted candidate.

I am relatively new to IEEE-754 and this area of the code so any comments would be appreciated.

Thanks,

Brian

diff -r 1405ad6afb1e -r 36482ed9bb7e src/share/classes/sun/misc/FloatingDecimal.java
--- a/src/share/classes/sun/misc/FloatingDecimal.java	Thu Feb 14 11:09:07 2013 -0800
+++ b/src/share/classes/sun/misc/FloatingDecimal.java	Thu Feb 14 16:47:53 2013 -0800
@@ -35,8 +35,16 @@
     int         decExponent;
     char        digits[];
     int         nDigits;
+
+    // = ((doubleAsLongBits & expMask) >>> expShift) - expBias + 1 - bigIntNBits
+    // Set by doubleToBigInt().
     int         bigIntExp;
+
+    // Number of bits from the high order 1 bit to the low order 1 bit,
+    // inclusive, of the fractional (significand) part of a double.
+    // Set by doubleToBigInt().
     int         bigIntNBits;
+
     boolean     mustSetRoundDir = false;
     boolean     fromHex = false;
     int         roundDir = 0; // set by doubleValue
@@ -1604,7 +1612,50 @@
                 } else if ( cmpResult == 0 ){
                     // difference is exactly half an ULP
                     // round to some other value maybe, then finish
-                    dValue += 0.5*ulp( dValue, overvalue );
+                    // Fix of bug 4396272. This method has strictfp modifier now.
+                    // Nevertheless, the two comments below explain why it can work without strictfp too.
+                    //
+                    // In the logical expression tested on the if-block,
+                    // bigIntExp = expBiased - expBias + 1 - bigIntNBits so that
+                    // bigIntExp+bigIntNBits = expBiased - expBias + 1 so the inequality tested is
+                    // expBiased - expBias + 1 > -expBias+2 or expBiased > 1 which implies
+                    // exUnbiased > 1 - expBias = -1022 or expUnbiased > -1021 so that
+                    // old dValue > 2^(-1021) = 2*2^(-1022) = 2*Double.MIN_NORMAL.
+                    // Either ">" or ">=" could be used in the logical expression
+                    // as both branches evaluate to the same result when equality obtains.
+                    //
+                    // As dValue becomes smaller, the ULP descreases reaching
+                    // Double.MIN_VALUE for dValue == 2*Double.MIN_NORMAL. Below
+                    // this the intermediate result 0.5*ULP will be rounded to
+                    // zero per FP-strict. As a result no correction to dValue
+                    // will occur. To prevent this, 2*dValue + ULP is first
+                    // computed so that the effect of adding half the ULP is not
+                    // lost, then this intermediate result is halved.
+                    //
+                    if ( bigIntExp+bigIntNBits > -expBias+2 ) {
+                        // Here old dValue > 2*Double.MIN_NORMAL
+                        //
+                        // Non-FP-strict case:
+                        // If overvalue == false then ulp(dValue,false) >= 2*Double.MIN_VALUE and 0.5*ulp is exact.
+                        // If overvalue == true and dValue > 2*Double.MIN_NORMAL then ulp(dValue,true) <= -2*Double.MIN_VALUE and 0.5*ulp is exact.
+                        // If overvalue == true and dValue == 2*Double.MIN_NORMAL then ulp(dValue,true) == -Double.MIN_VALUE.
+                        //   Hence 0.5*ulp is rounded to 0 in double value set and 0.5*ulp is exact in double-extended-exponent value set.
+                        //   In both value sets new dValue is rounded to 2*Double.MIN_NOPMAL as expected
+                        dValue += 0.5*ulp( dValue, overvalue );
+                    } else {
+                        // Here old dValue <= 2*Double.MIN_NORMAL
+                        //
+                        // Non-FP-strict case:
+                        // If overvalue == false then ulp(dValue,false) == Double.MIN_VALUE
+                        //   If dValue >= Double.MIN_NORMAL then dValue*2 + ulp rounds to nearest even and 0.5*(...) is exact
+                        //   If dValue < Double.MIN_NORMAL then dValue*2 + ulp is exact and 0.5*(...) rounds to nearest even in double value set
+                        //     and is exact in double-extended-exponent value set and then rounds to nearest even double before storing to new dValue. 
+                        // If overvalue == true then ulp(dValue,true) == -Double.MIN_VALUE
+                        //   If dValue > Double.MIN_NORMAL then dValue*2 + ulp rounds to nearest even and 0.5*(...) is exact
+                        //   If dValue <= Double.MIN_NORMAL then dValue*2 + ulp is exact and 0.5*(...) rounds to nearest even in double value set
+                        //     and is exact in double-extended-exponent value set and then rounds to nearest even double before storing to new dValue. 
+			dValue = 0.5*(dValue*2 + ulp( dValue, overvalue));
+                    }
                     // should check for bigIntNBits == 1 here??
                     if (mustSetRoundDir) {
                         roundDir = overvalue ? -1 : 1;
diff -r 1405ad6afb1e -r 36482ed9bb7e test/java/lang/Double/ParseDouble.java
--- a/test/java/lang/Double/ParseDouble.java	Thu Feb 14 11:09:07 2013 -0800
+++ b/test/java/lang/Double/ParseDouble.java	Thu Feb 14 16:47:53 2013 -0800
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 4160406 4705734 4707389 4826774 4895911 4421494 7021568 7039369
+ * @bug 4160406 4705734 4707389 4826774 4895911 4421494 7021568 7039369 4396272
  * @summary Test for Double.parseDouble method and acceptance regex
  */
 
@@ -560,17 +560,18 @@
      * region that should convert to that value.
      */
     private static void testSubnormalPowers() {
+        boolean failed = false;
         BigDecimal TWO = BigDecimal.valueOf(2);
         // An ulp is the same for all subnormal values
         BigDecimal ulp_BD = new BigDecimal(Double.MIN_VALUE);
 
-        // Test subnormal powers of two
-        for(int i = -1074; i <= -1022; i++) {
+        // Test subnormal powers of two (except Double.MIN_VALUE)
+        for(int i = -1073; i <= -1022; i++) {
             double d = Math.scalb(1.0, i);
 
             /*
              * The region [d - ulp/2, d + ulp/2] should round to d.
-             */
+            */
             BigDecimal d_BD = new BigDecimal(d);
 
             BigDecimal lowerBound = d_BD.subtract(ulp_BD.divide(TWO));
@@ -578,17 +579,52 @@
 
             double convertedLowerBound = Double.parseDouble(lowerBound.toString());
             double convertedUpperBound = Double.parseDouble(upperBound.toString());
+            if (convertedLowerBound != d) {
+                failed = true;
+                System.out.printf("2^%d lowerBound converts as %a %s%n",
+                                  i, convertedLowerBound, lowerBound);
+            }
+            if (convertedUpperBound != d) {
+                failed = true;
+                System.out.printf("2^%d upperBound converts as %a %s%n",
+                                  i, convertedUpperBound, upperBound);
+            }
         }
+        /*
+         * Double.MIN_VALUE
+         * The region ]0.5*Double.MIN_VALUE, 1.5*Double.MIN_VALUE[ should round to Double.MIN_VALUE .
+         */
+        BigDecimal minValue = new BigDecimal(Double.MIN_VALUE);
+        if (Double.parseDouble(minValue.multiply(new BigDecimal(0.5)).toString()) != 0.0) {
+            failed = true;
+            System.out.printf("0.5*MIN_VALUE doesn't convert 0%n");
+        }
+        if (Double.parseDouble(minValue.multiply(new BigDecimal(0.50000000001)).toString()) != Double.MIN_VALUE) {
+            failed = true;
+            System.out.printf("0.50000000001*MIN_VALUE doesn't convert to MIN_VALUE%n");
+        }
+        if (Double.parseDouble(minValue.multiply(new BigDecimal(1.49999999999)).toString()) != Double.MIN_VALUE) {
+            failed = true;
+            System.out.printf("1.49999999999*MIN_VALUE doesn't convert to MIN_VALUE%n");
+        }
+        if (Double.parseDouble(minValue.multiply(new BigDecimal(1.5)).toString()) != 2*Double.MIN_VALUE) {
+            failed = true;
+            System.out.printf("1.5*MIN_VALUE doesn't convert to 2*MIN_VALUE%n");
+        }
+
+        if (failed)
+            throw new RuntimeException("Inconsistent conversion");
     }
 
 
     private static void testStrictness() {
-        final double expected = 0x0.0000008000001p-1022;
+        final double expected = 0x0.0000008000000p-1022;
+//        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
+        //2^-1047 + 2^-1075 rounds to 2^-1047
         String decimal = "6.631236871469758276785396630275967243399099947355303144249971758736286630139265439618068200788048744105960420552601852889715006376325666595539603330361800519107591783233358492337208057849499360899425128640718856616503093444922854759159988160304439909868291973931426625698663157749836252274523485312442358651207051292453083278116143932569727918709786004497872322193856150225415211997283078496319412124640111777216148110752815101775295719811974338451936095907419622417538473679495148632480391435931767981122396703443803335529756003353209830071832230689201383015598792184172909927924176339315507402234836120730914783168400715462440053817592702766213559042115986763819482654128770595766806872783349146967171293949598850675682115696218943412532098591327667236328125E-316";
 
         for(int i = 0; i <= 12_000; i++) {




More information about the core-libs-dev mailing list