6908131: Pure Java implementations of StrictMath.floor(double) & StrictMath.ceil(double)
authordarcy
Tue, 15 Dec 2009 13:51:44 -0800
changeset 4520 d2c9d06b5b31
parent 4519 fff7fde5b3a0
child 4521 6562b91703be
child 4522 f0ae9e22d8ca
6908131: Pure Java implementations of StrictMath.floor(double) & StrictMath.ceil(double) Reviewed-by: alanb
jdk/make/java/java/mapfile-vers
jdk/make/java/java/reorder-i586
jdk/make/java/java/reorder-sparc
jdk/make/java/java/reorder-sparcv9
jdk/src/share/classes/java/lang/StrictMath.java
jdk/src/share/native/java/lang/StrictMath.c
jdk/test/java/lang/Math/CeilAndFloorTests.java
--- a/jdk/make/java/java/mapfile-vers	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/make/java/java/mapfile-vers	Tue Dec 15 13:51:44 2009 -0800
@@ -150,10 +150,8 @@
 		Java_java_lang_StrictMath_asin;
 		Java_java_lang_StrictMath_atan;
 		Java_java_lang_StrictMath_atan2;
-		Java_java_lang_StrictMath_ceil;
 		Java_java_lang_StrictMath_cos;
 		Java_java_lang_StrictMath_exp;
-		Java_java_lang_StrictMath_floor;
 		Java_java_lang_StrictMath_log;
 		Java_java_lang_StrictMath_log10;
 		Java_java_lang_StrictMath_pow;
--- a/jdk/make/java/java/reorder-i586	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/make/java/java/reorder-i586	Tue Dec 15 13:51:44 2009 -0800
@@ -107,4 +107,3 @@
 text: .text%Java_java_util_TimeZone_getSystemTimeZoneID;
 text: .text%findJavaTZ_md;
 text: .text%Java_java_lang_StrictMath_log;
-text: .text%Java_java_lang_StrictMath_floor;
--- a/jdk/make/java/java/reorder-sparc	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/make/java/java/reorder-sparc	Tue Dec 15 13:51:44 2009 -0800
@@ -105,4 +105,3 @@
 text: .text%findJavaTZ_md;
 text: .text%Java_java_lang_StrictMath_log;
 text: .text%Java_java_lang_StrictMath_sqrt;
-text: .text%Java_java_lang_StrictMath_floor;
--- a/jdk/make/java/java/reorder-sparcv9	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/make/java/java/reorder-sparcv9	Tue Dec 15 13:51:44 2009 -0800
@@ -101,4 +101,3 @@
 text: .text%findJavaTZ_md;
 text: .text%Java_java_lang_StrictMath_log;
 text: .text%Java_java_lang_StrictMath_sqrt;
-text: .text%Java_java_lang_StrictMath_floor;
--- a/jdk/src/share/classes/java/lang/StrictMath.java	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/src/share/classes/java/lang/StrictMath.java	Tue Dec 15 13:51:44 2009 -0800
@@ -26,6 +26,7 @@
 package java.lang;
 import java.util.Random;
 import sun.misc.FpUtils;
+import sun.misc.DoubleConsts;
 
 /**
  * The class {@code StrictMath} contains methods for performing basic
@@ -316,7 +317,9 @@
      *          floating-point value that is greater than or equal to
      *          the argument and is equal to a mathematical integer.
      */
-    public static native double ceil(double a);
+    public static double ceil(double a) {
+        return floorOrCeil(a, -0.0, 1.0, 1.0);
+    }
 
     /**
      * Returns the largest (closest to positive infinity)
@@ -333,7 +336,54 @@
      *          floating-point value that less than or equal to the argument
      *          and is equal to a mathematical integer.
      */
-    public static native double floor(double a);
+    public static double floor(double a) {
+        return floorOrCeil(a, -1.0, 0.0, -1.0);
+    }
+
+    /**
+     * Internal method to share logic between floor and ceil.
+     *
+     * @param a the value to be floored or ceiled
+     * @param negativeBoundary result for values in (-1, 0)
+     * @param positiveBoundary result for values in (0, 1)
+     * @param increment value to add when the argument is non-integral
+     */
+    private static double floorOrCeil(double a,
+                                      double negativeBoundary,
+                                      double positiveBoundary,
+                                      double sign) {
+        int exponent = Math.getExponent(a);
+
+        if (exponent < 0) {
+            /*
+             * Absolute value of argument is less than 1.
+             * floorOrceil(-0.0) => -0.0
+             * floorOrceil(+0.0) => +0.0
+             */
+            return ((a == 0.0) ? a :
+                    ( (a < 0.0) ?  negativeBoundary : positiveBoundary) );
+        } else if (exponent >= 52) {
+            /*
+             * Infinity, NaN, or a value so large it must be integral.
+             */
+            return a;
+        }
+        // Else the argument is either an integral value already XOR it
+        // has to be rounded to one.
+        assert exponent >= 0 && exponent <= 51;
+
+        long doppel = Double.doubleToRawLongBits(a);
+        long mask   = DoubleConsts.SIGNIF_BIT_MASK >> exponent;
+
+        if ( (mask & doppel) == 0L )
+            return a; // integral value
+        else {
+            double result = Double.longBitsToDouble(doppel & (~mask));
+            if (sign*a > 0.0)
+                result = result + sign;
+            return result;
+        }
+    }
 
     /**
      * Returns the {@code double} value that is closest in value
--- a/jdk/src/share/native/java/lang/StrictMath.c	Fri Dec 11 17:18:38 2009 -0800
+++ b/jdk/src/share/native/java/lang/StrictMath.c	Tue Dec 15 13:51:44 2009 -0800
@@ -95,18 +95,6 @@
 }
 
 JNIEXPORT jdouble JNICALL
-Java_java_lang_StrictMath_ceil(JNIEnv *env, jclass unused, jdouble d)
-{
-    return (jdouble) jceil((double)d);
-}
-
-JNIEXPORT jdouble JNICALL
-Java_java_lang_StrictMath_floor(JNIEnv *env, jclass unused, jdouble d)
-{
-    return (jdouble) jfloor((double)d);
-}
-
-JNIEXPORT jdouble JNICALL
 Java_java_lang_StrictMath_atan2(JNIEnv *env, jclass unused, jdouble d1, jdouble d2)
 {
     return (jdouble) jatan2((double)d1, (double)d2);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/Math/CeilAndFloorTests.java	Tue Dec 15 13:51:44 2009 -0800
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6908131
+ * @summary Check for correct implementation of Math.ceil and Math.floor
+ */
+
+import sun.misc.FpUtils;
+import sun.misc.DoubleConsts;
+
+public class CeilAndFloorTests {
+    private static int testCeilCase(double input, double expected) {
+        int failures = 0;
+        failures += Tests.test("Math.ceil",  input, Math.ceil(input),   expected);
+        failures += Tests.test("StrictMath.ceil",  input, StrictMath.ceil(input), expected);
+        return failures;
+    }
+
+    private static int testFloorCase(double input, double expected) {
+        int failures = 0;
+        failures += Tests.test("Math.floor",  input, Math.floor(input),   expected);
+        failures += Tests.test("StrictMath.floor",  input, StrictMath.floor(input), expected);
+        return failures;
+    }
+
+    private static int nearIntegerTests() {
+        int failures = 0;
+
+        double [] fixedPoints = {
+            -0.0,
+             0.0,
+            -1.0,
+             1.0,
+            -0x1.0p52,
+             0x1.0p52,
+            -Double.MAX_VALUE,
+             Double.MAX_VALUE,
+             Double.NEGATIVE_INFINITY,
+             Double.POSITIVE_INFINITY,
+             Double.NaN,
+        };
+
+        for(double fixedPoint : fixedPoints) {
+            failures += testCeilCase(fixedPoint, fixedPoint);
+            failures += testFloorCase(fixedPoint, fixedPoint);
+        }
+
+        for(int i = Double.MIN_EXPONENT; i <= Double.MAX_EXPONENT; i++) {
+            double powerOfTwo   = Math.scalb(1.0, i);
+            double neighborDown = FpUtils.nextDown(powerOfTwo);
+            double neighborUp   = Math.nextUp(powerOfTwo);
+
+            if (i < 0) {
+                failures += testCeilCase( powerOfTwo,  1.0);
+                failures += testCeilCase(-powerOfTwo, -0.0);
+
+                failures += testFloorCase( powerOfTwo,  0.0);
+                failures += testFloorCase(-powerOfTwo, -1.0);
+
+                failures += testCeilCase( neighborDown, 1.0);
+                failures += testCeilCase(-neighborDown, -0.0);
+
+                failures += testFloorCase( neighborUp,  0.0);
+                failures += testFloorCase(-neighborUp, -1.0);
+            } else {
+                failures += testCeilCase(powerOfTwo, powerOfTwo);
+                failures += testFloorCase(powerOfTwo, powerOfTwo);
+
+                if (neighborDown==Math.rint(neighborDown)) {
+                    failures += testCeilCase( neighborDown,  neighborDown);
+                    failures += testCeilCase(-neighborDown, -neighborDown);
+
+                    failures += testFloorCase( neighborDown, neighborDown);
+                    failures += testFloorCase(-neighborDown,-neighborDown);
+                } else {
+                    failures += testCeilCase( neighborDown, powerOfTwo);
+                    failures += testFloorCase(-neighborDown, -powerOfTwo);
+                }
+
+                if (neighborUp==Math.rint(neighborUp)) {
+                    failures += testCeilCase(neighborUp, neighborUp);
+                    failures += testCeilCase(-neighborUp, -neighborUp);
+
+                    failures += testFloorCase(neighborUp, neighborUp);
+                    failures += testFloorCase(-neighborUp, -neighborUp);
+                } else {
+                    failures += testFloorCase(neighborUp, powerOfTwo);
+                    failures += testCeilCase(-neighborUp, -powerOfTwo);
+                }
+            }
+        }
+
+        for(int i = -(0x10000); i <= 0x10000; i++) {
+            double d = (double) i;
+            double neighborDown = FpUtils.nextDown(d);
+            double neighborUp   = Math.nextUp(d);
+
+            failures += testCeilCase( d, d);
+            failures += testCeilCase(-d, -d);
+
+            failures += testFloorCase( d, d);
+            failures += testFloorCase(-d, -d);
+
+            if (Math.abs(d) > 1.0) {
+                failures += testCeilCase( neighborDown, d);
+                failures += testCeilCase(-neighborDown, -d+1);
+
+                failures += testFloorCase( neighborUp, d);
+                failures += testFloorCase(-neighborUp, -d-1);
+            }
+        }
+
+        return failures;
+    }
+
+    public static int roundingTests() {
+        int failures = 0;
+        double [][] testCases = {
+            { Double.MIN_VALUE,                           1.0},
+            {-Double.MIN_VALUE,                          -0.0},
+            { FpUtils.nextDown(DoubleConsts.MIN_NORMAL),  1.0},
+            {-FpUtils.nextDown(DoubleConsts.MIN_NORMAL), -0.0},
+            { DoubleConsts.MIN_NORMAL,                    1.0},
+            {-DoubleConsts.MIN_NORMAL,                   -0.0},
+
+            { 0.1,                                        1.0},
+            {-0.1,                                       -0.0},
+
+            { 0.5,                                        1.0},
+            {-0.5,                                       -0.0},
+
+            { 1.5,                                        2.0},
+            {-1.5,                                       -1.0},
+
+            { 2.5,                                        3.0},
+            {-2.5,                                       -2.0},
+
+            { FpUtils.nextDown(1.0),                      1.0},
+            { FpUtils.nextDown(-1.0),                    -1.0},
+
+            { Math.nextUp(1.0),                           2.0},
+            { Math.nextUp(-1.0),                         -0.0},
+
+            { 0x1.0p51,                                 0x1.0p51},
+            {-0x1.0p51,                                -0x1.0p51},
+
+            { FpUtils.nextDown(0x1.0p51),               0x1.0p51},
+            {-Math.nextUp(0x1.0p51),                   -0x1.0p51},
+
+            { Math.nextUp(0x1.0p51),                    0x1.0p51+1},
+            {-FpUtils.nextDown(0x1.0p51),              -0x1.0p51+1},
+
+            { FpUtils.nextDown(0x1.0p52),               0x1.0p52},
+            {-Math.nextUp(0x1.0p52),                   -0x1.0p52-1.0},
+
+            { Math.nextUp(0x1.0p52),                    0x1.0p52+1.0},
+            {-FpUtils.nextDown(0x1.0p52),              -0x1.0p52+1.0},
+        };
+
+        for(double[] testCase : testCases) {
+            failures += testCeilCase(testCase[0], testCase[1]);
+            failures += testFloorCase(-testCase[0], -testCase[1]);
+        }
+        return failures;
+    }
+
+    public static void main(String... args) {
+        int failures = 0;
+
+        failures += nearIntegerTests();
+        failures += roundingTests();
+
+        if (failures > 0) {
+            System.err.println("Testing {Math, StrictMath}.ceil incurred "
+                               + failures + " failures.");
+            throw new RuntimeException();
+        }
+    }
+}