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++) {