8010430: Math.round has surprising behavior for odd values of ulp 1
authorbpb
Wed, 11 Sep 2013 17:07:35 -0700
changeset 19851 7b6ff45c39ce
parent 19850 93b368e54c1c
child 19852 f8e5a6c5d379
8010430: Math.round has surprising behavior for odd values of ulp 1 Summary: If the effective floating point exponent is zero return the significand including the implicit 1-bit. Reviewed-by: bpb, darcy, gls Contributed-by: Dmitry Nadezhin <dmitry.nadezhin@oracle.com>
jdk/src/share/classes/java/lang/Math.java
jdk/src/share/classes/java/lang/StrictMath.java
jdk/test/java/lang/Math/RoundTests.java
--- a/jdk/src/share/classes/java/lang/Math.java	Fri Sep 06 22:20:01 2013 -0700
+++ b/jdk/src/share/classes/java/lang/Math.java	Wed Sep 11 17:07:35 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2013, 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
@@ -646,7 +646,7 @@
 
     /**
      * Returns the closest {@code int} to the argument, with ties
-     * rounding up.
+     * rounding to positive infinity.
      *
      * <p>
      * Special cases:
@@ -665,15 +665,37 @@
      * @see     java.lang.Integer#MIN_VALUE
      */
     public static int round(float a) {
-        if (a != 0x1.fffffep-2f) // greatest float value less than 0.5
-            return (int)floor(a + 0.5f);
-        else
-            return 0;
+        int intBits = Float.floatToRawIntBits(a);
+        int biasedExp = (intBits & FloatConsts.EXP_BIT_MASK)
+                >> (FloatConsts.SIGNIFICAND_WIDTH - 1);
+        int shift = (FloatConsts.SIGNIFICAND_WIDTH - 2
+                + FloatConsts.EXP_BIAS) - biasedExp;
+        if ((shift & -32) == 0) { // shift >= 0 && shift < 32
+            // a is a finite number such that pow(2,-32) <= ulp(a) < 1
+            int r = ((intBits & FloatConsts.SIGNIF_BIT_MASK)
+                    | (FloatConsts.SIGNIF_BIT_MASK + 1));
+            if (intBits < 0) {
+                r = -r;
+            }
+            // In the comments below each Java expression evaluates to the value
+            // the corresponding mathematical expression:
+            // (r) evaluates to a / ulp(a)
+            // (r >> shift) evaluates to floor(a * 2)
+            // ((r >> shift) + 1) evaluates to floor((a + 1/2) * 2)
+            // (((r >> shift) + 1) >> 1) evaluates to floor(a + 1/2)
+            return ((r >> shift) + 1) >> 1;
+        } else {
+            // a is either
+            // - a finite number with abs(a) < exp(2,FloatConsts.SIGNIFICAND_WIDTH-32) < 1/2
+            // - a finite number with ulp(a) >= 1 and hence a is a mathematical integer
+            // - an infinity or NaN
+            return (int) a;
+        }
     }
 
     /**
      * Returns the closest {@code long} to the argument, with ties
-     * rounding up.
+     * rounding to positive infinity.
      *
      * <p>Special cases:
      * <ul><li>If the argument is NaN, the result is 0.
@@ -692,10 +714,32 @@
      * @see     java.lang.Long#MIN_VALUE
      */
     public static long round(double a) {
-        if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
-            return (long)floor(a + 0.5d);
-        else
-            return 0;
+        long longBits = Double.doubleToRawLongBits(a);
+        long biasedExp = (longBits & DoubleConsts.EXP_BIT_MASK)
+                >> (DoubleConsts.SIGNIFICAND_WIDTH - 1);
+        long shift = (DoubleConsts.SIGNIFICAND_WIDTH - 2
+                + DoubleConsts.EXP_BIAS) - biasedExp;
+        if ((shift & -64) == 0) { // shift >= 0 && shift < 64
+            // a is a finite number such that pow(2,-64) <= ulp(a) < 1
+            long r = ((longBits & DoubleConsts.SIGNIF_BIT_MASK)
+                    | (DoubleConsts.SIGNIF_BIT_MASK + 1));
+            if (longBits < 0) {
+                r = -r;
+            }
+            // In the comments below each Java expression evaluates to the value
+            // the corresponding mathematical expression:
+            // (r) evaluates to a / ulp(a)
+            // (r >> shift) evaluates to floor(a * 2)
+            // ((r >> shift) + 1) evaluates to floor((a + 1/2) * 2)
+            // (((r >> shift) + 1) >> 1) evaluates to floor(a + 1/2)
+            return ((r >> shift) + 1) >> 1;
+        } else {
+            // a is either
+            // - a finite number with abs(a) < exp(2,DoubleConsts.SIGNIFICAND_WIDTH-64) < 1/2
+            // - a finite number with ulp(a) >= 1 and hence a is a mathematical integer
+            // - an infinity or NaN
+            return (long) a;
+        }
     }
 
     private static final class RandomNumberGeneratorHolder {
--- a/jdk/src/share/classes/java/lang/StrictMath.java	Fri Sep 06 22:20:01 2013 -0700
+++ b/jdk/src/share/classes/java/lang/StrictMath.java	Wed Sep 11 17:07:35 2013 -0700
@@ -633,7 +633,7 @@
 
     /**
      * Returns the closest {@code int} to the argument, with ties
-     * rounding up.
+     * rounding to positive infinity.
      *
      * <p>Special cases:
      * <ul><li>If the argument is NaN, the result is 0.
@@ -656,7 +656,7 @@
 
     /**
      * Returns the closest {@code long} to the argument, with ties
-     * rounding up.
+     * rounding to positive infinity.
      *
      * <p>Special cases:
      * <ul><li>If the argument is NaN, the result is 0.
--- a/jdk/test/java/lang/Math/RoundTests.java	Fri Sep 06 22:20:01 2013 -0700
+++ b/jdk/test/java/lang/Math/RoundTests.java	Wed Sep 11 17:07:35 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2013, 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
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 6430675
+ * @bug 6430675 8010430
  * @summary Check for correct implementation of {Math, StrictMath}.round
  */
 public class RoundTests {
@@ -32,6 +32,8 @@
 
         failures += testNearFloatHalfCases();
         failures += testNearDoubleHalfCases();
+        failures += testUnityULPCases();
+        failures += testSpecialCases();
 
         if (failures > 0) {
             System.err.println("Testing {Math, StrictMath}.round incurred "
@@ -95,4 +97,69 @@
 
         return failures;
     }
+
+    private static int testUnityULPCases() {
+        int failures = 0;
+        for (float sign : new float[]{-1, 1}) {
+            for (float v1 : new float[]{1 << 23, 1 << 24}) {
+                for (int k = -5; k <= 5; k++) {
+                    float value = (v1 + k) * sign;
+                    float actual = Math.round(value);
+                    failures += Tests.test("Math.round", value, actual, value);
+                }
+            }
+        }
+
+        if (failures != 0) {
+            System.out.println();
+        }
+
+        for (double sign : new double[]{-1, 1}) {
+            for (double v1 : new double[]{1L << 52, 1L << 53}) {
+                for (int k = -5; k <= 5; k++) {
+                    double value = (v1 + k) * sign;
+                    double actual = Math.round(value);
+                    failures += Tests.test("Math.round", value, actual, value);
+                }
+            }
+        }
+
+        return failures;
+    }
+
+    private static int testSpecialCases() {
+        int failures = 0;
+
+        failures += Tests.test("Math.round", Float.NaN, Math.round(Float.NaN), 0.0F);
+        failures += Tests.test("Math.round", Float.POSITIVE_INFINITY,
+                Math.round(Float.POSITIVE_INFINITY), Integer.MAX_VALUE);
+        failures += Tests.test("Math.round", Float.NEGATIVE_INFINITY,
+                Math.round(Float.NEGATIVE_INFINITY), Integer.MIN_VALUE);
+        failures += Tests.test("Math.round", -(float)Integer.MIN_VALUE,
+                Math.round(-(float)Integer.MIN_VALUE), Integer.MAX_VALUE);
+        failures += Tests.test("Math.round", (float) Integer.MIN_VALUE,
+                Math.round((float) Integer.MIN_VALUE), Integer.MIN_VALUE);
+        failures += Tests.test("Math.round", 0F, Math.round(0F), 0.0F);
+        failures += Tests.test("Math.round", Float.MIN_VALUE,
+                Math.round(Float.MIN_VALUE), 0.0F);
+        failures += Tests.test("Math.round", -Float.MIN_VALUE,
+                Math.round(-Float.MIN_VALUE), 0.0F);
+
+        failures += Tests.test("Math.round", Double.NaN, Math.round(Double.NaN), 0.0);
+        failures += Tests.test("Math.round", Double.POSITIVE_INFINITY,
+                Math.round(Double.POSITIVE_INFINITY), Long.MAX_VALUE);
+        failures += Tests.test("Math.round", Double.NEGATIVE_INFINITY,
+                Math.round(Double.NEGATIVE_INFINITY), Long.MIN_VALUE);
+        failures += Tests.test("Math.round", -(double)Long.MIN_VALUE,
+                Math.round(-(double)Long.MIN_VALUE), Long.MAX_VALUE);
+        failures += Tests.test("Math.round", (double) Long.MIN_VALUE,
+                Math.round((double) Long.MIN_VALUE), Long.MIN_VALUE);
+        failures += Tests.test("Math.round", 0, Math.round(0), 0.0);
+        failures += Tests.test("Math.round", Double.MIN_VALUE,
+                Math.round(Double.MIN_VALUE), 0.0);
+        failures += Tests.test("Math.round", -Double.MIN_VALUE,
+                Math.round(-Double.MIN_VALUE), 0.0);
+
+        return failures;
+    }
 }