JDK-6430675 : Math.round has surprising behavior for 0x1.fffffffffffffp-2
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2006-05-25
  • Updated: 2017-05-16
  • 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
The methods {Math, StrictMath.round are operationally defined as

	(long)Math.floor(a + 0.5d)

for double arguments.  While this definition usually works as expected, it gives the surprising result of 1, rather than 0, for 0x1.fffffffffffffp-2 (0.49999999999999994).

The value 0.49999999999999994 is the greatest floating-point value less than 0.5.  As a hexadecimal floating-point literal its value is 0x1.fffffffffffffp-2, which is equal to (2 - 2^52) * 2^-2. == (0.5 - 2^54).  Therefore, the exact value of the sum

	(0.5 - 2^54) + 0.5

is 1 - 2^54.  This is halfway between the two adjacent floating-point numbers (1 - 2^53) and 1.  In the IEEE 754 arithmetic round to nearest even rounding mode used by Java, when a floating-point results is inexact, the closer of the two representable floating-point values which bracket the exact result must be returned; if both values are equally close, the one which its last bit zero is returned.  In this case the correct return value from the add is 1, not the greatest value less than 1.

While the method is operating as defined, the behavior on this input is very surprising; the specification coudl be ameneded to something more like "Round to the closest long, rounding ties up," which would allow the behavior on this input to be changed.

Comments
SUGGESTED FIX # HG changeset patch # User darcy # Date 1302841630 25200 # Node ID 2d89d0d1e0ff8404737ece176d9649620afa398b # Parent e9ae2178926af046537522263e7c15ce84f7bd0b 6430675: Math.round has surprising behavior for 0x1.fffffffffffffp-2 Reviewed-by: alanb --- a/src/share/classes/java/lang/Math.java Thu Apr 14 12:40:30 2011 +0800 +++ b/src/share/classes/java/lang/Math.java Thu Apr 14 21:27:10 2011 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -627,11 +627,9 @@ public final class Math { } /** - * Returns the closest {@code int} to the argument. The - * result is rounded to an integer by adding 1/2, taking the - * floor of the result, and casting the result to type {@code int}. - * In other words, the result is equal to the value of the expression: - * <p>{@code (int)Math.floor(a + 0.5f)} + * Returns the closest {@code int} to the argument, with ties + * rounding up. + * * <p> * Special cases: * <ul><li>If the argument is NaN, the result is 0. @@ -649,17 +647,17 @@ public final class Math { * @see java.lang.Integer#MIN_VALUE */ public static int round(float a) { - return (int)floor(a + 0.5f); - } - - /** - * Returns the closest {@code long} to the argument. The result - * is rounded to an integer by adding 1/2, taking the floor of the - * result, and casting the result to type {@code long}. In other - * words, the result is equal to the value of the expression: - * <p>{@code (long)Math.floor(a + 0.5d)} - * <p> - * Special cases: + if (a != 0x1.fffffep-2f) // greatest float value less than 0.5 + return (int)floor(a + 0.5f); + else + return 0; + } + + /** + * Returns the closest {@code long} to the argument, with ties + * rounding up. + * + * <p>Special cases: * <ul><li>If the argument is NaN, the result is 0. * <li>If the argument is negative infinity or any value less than or * equal to the value of {@code Long.MIN_VALUE}, the result is @@ -676,7 +674,10 @@ public final class Math { * @see java.lang.Long#MIN_VALUE */ public static long round(double a) { - return (long)floor(a + 0.5d); + if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5 + return (long)floor(a + 0.5d); + else + return 0; } private static Random randomNumberGenerator; --- a/src/share/classes/java/lang/StrictMath.java Thu Apr 14 12:40:30 2011 +0800 +++ b/src/share/classes/java/lang/StrictMath.java Thu Apr 14 21:27:10 2011 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 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 @@ -613,11 +613,8 @@ public final class StrictMath { public static native double pow(double a, double b); /** - * Returns the closest {@code int} to the argument. The - * result is rounded to an integer by adding 1/2, taking the - * floor of the result, and casting the result to type {@code int}. - * In other words, the result is equal to the value of the expression: - * <p>{@code (int)Math.floor(a + 0.5f)} + * Returns the closest {@code int} to the argument, with ties + * rounding up. * * <p>Special cases: * <ul><li>If the argument is NaN, the result is 0. @@ -635,15 +632,12 @@ public final class StrictMath { * @see java.lang.Integer#MIN_VALUE */ public static int round(float a) { - return (int)floor(a + 0.5f); - } - - /** - * Returns the closest {@code long} to the argument. The result - * is rounded to an integer by adding 1/2, taking the floor of the - * result, and casting the result to type {@code long}. In other - * words, the result is equal to the value of the expression: - * <p>{@code (long)Math.floor(a + 0.5d)} + return Math.round(a); + } + + /** + * Returns the closest {@code long} to the argument, with ties + * rounding up. * * <p>Special cases: * <ul><li>If the argument is NaN, the result is 0. @@ -662,7 +656,7 @@ public final class StrictMath { * @see java.lang.Long#MIN_VALUE */ public static long round(double a) { - return (long)floor(a + 0.5d); + return Math.round(a); } private static Random randomNumberGenerator; --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/lang/Math/RoundTests.java Thu Apr 14 21:27:10 2011 -0700 @@ -0,0 +1,98 @@ +/* + * Copyright (c) 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 + * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6430675 + * @summary Check for correct implementation of {Math, StrictMath}.round + */ +public class RoundTests { + public static void main(String... args) { + int failures = 0; + + failures += testNearFloatHalfCases(); + failures += testNearDoubleHalfCases(); + + if (failures > 0) { + System.err.println("Testing {Math, StrictMath}.round incurred " + + failures + " failures."); + throw new RuntimeException(); + } + } + + private static int testNearDoubleHalfCases() { + int failures = 0; + double [][] testCases = { + {+0x1.fffffffffffffp-2, 0.0}, + {+0x1.0p-1, 1.0}, // +0.5 + {+0x1.0000000000001p-1, 1.0}, + + {-0x1.fffffffffffffp-2, 0.0}, + {-0x1.0p-1, 0.0}, // -0.5 + {-0x1.0000000000001p-1, -1.0}, + }; + + for(double[] testCase : testCases) { + failures += testNearHalfCases(testCase[0], (long)testCase[1]); + } + + return failures; + } + + private static int testNearHalfCases(double input, double expected) { + int failures = 0; + + failures += Tests.test("Math.round", input, Math.round(input), expected); + failures += Tests.test("StrictMath.round", input, StrictMath.round(input), expected); + + return failures; + } + + private static int testNearFloatHalfCases() { + int failures = 0; + float [][] testCases = { + {+0x1.fffffep-2f, 0.0f}, + {+0x1.0p-1f, 1.0f}, // +0.5 + {+0x1.000002p-1f, 1.0f}, + + {-0x1.fffffep-2f, 0.0f}, + {-0x1.0p-1f, 0.0f}, // -0.5 + {-0x1.000002p-1f, -1.0f}, + }; + + for(float[] testCase : testCases) { + failures += testNearHalfCases(testCase[0], (int)testCase[1]); + } + + return failures; + } + + private static int testNearHalfCases(float input, float expected) { + int failures = 0; + + failures += Tests.test("Math.round", input, Math.round(input), expected); + failures += Tests.test("StrictMath.round", input, StrictMath.round(input), expected); + + return failures; + } +}
15-04-2011

PUBLIC COMMENTS See http://hg.openjdk.java.net/jdk7/tl/jdk/rev/2d89d0d1e0ff
15-04-2011

EVALUATION A reasonable change to consider.
25-05-2006