Return-Path: Delivered-To: apmail-jakarta-commons-dev-archive@www.apache.org Received: (qmail 18946 invoked from network); 4 Jul 2006 08:09:52 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 4 Jul 2006 08:09:52 -0000 Received: (qmail 7502 invoked by uid 500); 4 Jul 2006 08:09:50 -0000 Delivered-To: apmail-jakarta-commons-dev-archive@jakarta.apache.org Received: (qmail 7170 invoked by uid 500); 4 Jul 2006 08:09:49 -0000 Mailing-List: contact commons-dev-help@jakarta.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Help: List-Post: List-Id: "Jakarta Commons Developers List" Reply-To: "Jakarta Commons Developers List" Delivered-To: mailing list commons-dev@jakarta.apache.org Received: (qmail 7159 invoked by uid 500); 4 Jul 2006 08:09:49 -0000 Received: (qmail 7155 invoked by uid 99); 4 Jul 2006 08:09:48 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 04 Jul 2006 01:09:48 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 04 Jul 2006 01:09:47 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 3D3B71A983A; Tue, 4 Jul 2006 01:09:27 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r418934 - in /jakarta/commons/proper/math/trunk: project.xml src/java/org/apache/commons/math/util/MathUtils.java src/test/org/apache/commons/math/util/MathUtilsTest.java xdocs/changes.xml Date: Tue, 04 Jul 2006 08:09:26 -0000 To: commons-cvs@jakarta.apache.org From: psteitz@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060704080927.3D3B71A983A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: psteitz Date: Tue Jul 4 01:09:25 2006 New Revision: 418934 URL: http://svn.apache.org/viewvc?rev=418934&view=rev Log: Added a nextAfter method in MathUtils to return the next machine-representable number in a specified direction from a given floating point number. Used this to ensure that MathUtils.round does not return incorrect results for numbers with bad IEEE754 representations. JIRA: MATH-151 Reported by Buza Zoltán Patch submitted by Luc Maisonobe Modified: jakarta/commons/proper/math/trunk/project.xml jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java jakarta/commons/proper/math/trunk/xdocs/changes.xml Modified: jakarta/commons/proper/math/trunk/project.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/project.xml?rev=418934&r1=418933&r2=418934&view=diff ============================================================================== --- jakarta/commons/proper/math/trunk/project.xml (original) +++ jakarta/commons/proper/math/trunk/project.xml Tue Jul 4 01:09:25 2006 @@ -165,6 +165,9 @@ Piotr Kochanski + Luc Maisonobe + + Fredrik Norin Modified: jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java?rev=418934&r1=418933&r2=418934&view=diff ============================================================================== --- jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java (original) +++ jakarta/commons/proper/math/trunk/src/java/org/apache/commons/math/util/MathUtils.java Tue Jul 4 01:09:25 2006 @@ -474,6 +474,55 @@ } /** + * Get the closest machine representable number + * from a number in some direction. + + * @param d base number + * @param direction (the only important thing is whether + * direction is greater or smaller than d) + * @return + */ + public static double nextAfter(double d, double direction) { + + // handling of some important special cases + if (Double.isNaN(d) || Double.isInfinite(d)) { + return d; + } else if (d == 0) { + return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE; + } + // special cases MAX_VALUE to infinity and MIN_VALUE to 0 + // are handled just as normal numbers + + // split the double in raw components + long bits = Double.doubleToLongBits(d); + long sign = bits & 0x8000000000000000L; + long exponent = bits & 0x7ff0000000000000L; + long mantissa = bits & 0x000fffffffffffffL; + + if (d * (direction - d) >= 0) { + // we should increase the mantissa + if (mantissa == 0x000fffffffffffffL) { + return Double.longBitsToDouble(sign | + (exponent + 0x0010000000000000L)); + } else { + return Double.longBitsToDouble(sign | + exponent | (mantissa + 1)); + } + } else { + // we should decrease the mantissa + if (mantissa == 0L) { + return Double.longBitsToDouble(sign | + (exponent - 0x0010000000000000L) | + 0x000fffffffffffffL); + } else { + return Double.longBitsToDouble(sign | + exponent | (mantissa - 1)); + } + } + + } + + /** * Round the given value to the specified number of decimal places. The * value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method. * @@ -499,9 +548,18 @@ * @since 1.1 */ public static double round(double x, int scale, int roundingMethod) { - double sign = indicator(x); - double factor = Math.pow(10.0, scale) * sign; - return roundUnscaled(x * factor, sign, roundingMethod) / factor; + try { + return (new BigDecimal + (new Double(x).toString()) + .setScale(scale, roundingMethod)) + .doubleValue(); + } catch (NumberFormatException ex) { + if (Double.isInfinite(x)) { + return x; + } else { + return Double.NaN; + } + } } /** @@ -552,23 +610,24 @@ switch (roundingMethod) { case BigDecimal.ROUND_CEILING : if (sign == -1) { - unscaled = Math.floor(unscaled); + unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY)); } else { - unscaled = Math.ceil(unscaled); + unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY)); } break; case BigDecimal.ROUND_DOWN : - unscaled = Math.floor(unscaled); + unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY)); break; case BigDecimal.ROUND_FLOOR : if (sign == -1) { - unscaled = Math.ceil(unscaled); + unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY)); } else { - unscaled = Math.floor(unscaled); + unscaled = Math.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY)); } break; case BigDecimal.ROUND_HALF_DOWN : { - double fraction = Math.abs(unscaled - Math.floor(unscaled)); + unscaled = nextAfter(unscaled, Double.NEGATIVE_INFINITY); + double fraction = unscaled - Math.floor(unscaled); if (fraction > 0.5) { unscaled = Math.ceil(unscaled); } else { @@ -577,7 +636,7 @@ break; } case BigDecimal.ROUND_HALF_EVEN : { - double fraction = Math.abs(unscaled - Math.floor(unscaled)); + double fraction = unscaled - Math.floor(unscaled); if (fraction > 0.5) { unscaled = Math.ceil(unscaled); } else if (fraction < 0.5) { @@ -593,7 +652,8 @@ break; } case BigDecimal.ROUND_HALF_UP : { - double fraction = Math.abs(unscaled - Math.floor(unscaled)); + unscaled = nextAfter(unscaled, Double.POSITIVE_INFINITY); + double fraction = unscaled - Math.floor(unscaled); if (fraction >= 0.5) { unscaled = Math.ceil(unscaled); } else { @@ -607,7 +667,7 @@ } break; case BigDecimal.ROUND_UP : - unscaled = Math.ceil(unscaled); + unscaled = Math.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY)); break; default : throw new IllegalArgumentException("Invalid rounding method."); Modified: jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java?rev=418934&r1=418933&r2=418934&view=diff ============================================================================== --- jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java (original) +++ jakarta/commons/proper/math/trunk/src/test/org/apache/commons/math/util/MathUtilsTest.java Tue Jul 4 01:09:25 2006 @@ -583,15 +583,100 @@ assertEquals(Float.NEGATIVE_INFINITY, MathUtils.round(Float.NEGATIVE_INFINITY, 2), 0.0f); } + public void testNextAfterSpecialCases() { + assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.NEGATIVE_INFINITY, 0))); + assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.POSITIVE_INFINITY, 0))); + assertTrue(Double.isNaN(MathUtils.nextAfter(Double.NaN, 0))); + assertTrue(Double.isInfinite(MathUtils.nextAfter( Double.MAX_VALUE, Double.POSITIVE_INFINITY))); + assertTrue(Double.isInfinite(MathUtils.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY))); + assertEquals( Double.MIN_VALUE, MathUtils.nextAfter(0, 1), 0); + assertEquals(-Double.MIN_VALUE, MathUtils.nextAfter(0, -1), 0); + assertEquals(0, MathUtils.nextAfter( Double.MIN_VALUE, -1), 0); + assertEquals(0, MathUtils.nextAfter(-Double.MIN_VALUE, 1), 0); + } + + public void testNextAfter() { + // 0x402fffffffffffff 0x404123456789abcd -> 4030000000000000 + assertEquals(16.0, MathUtils.nextAfter(15.999999999999998, 34.27555555555555), 0.0); + + // 0xc02fffffffffffff 0x404123456789abcd -> c02ffffffffffffe + assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 34.27555555555555), 0.0); + + // 0x402fffffffffffff 0x400123456789abcd -> 402ffffffffffffe + assertEquals(15.999999999999996, MathUtils.nextAfter(15.999999999999998, 2.142222222222222), 0.0); + + // 0xc02fffffffffffff 0x400123456789abcd -> c02ffffffffffffe + assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 2.142222222222222), 0.0); + + // 0x4020000000000000 0x404123456789abcd -> 4020000000000001 + assertEquals(8.000000000000002, MathUtils.nextAfter(8.0, 34.27555555555555), 0.0); + + // 0xc020000000000000 0x404123456789abcd -> c01fffffffffffff + assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 34.27555555555555), 0.0); + + // 0x4020000000000000 0x400123456789abcd -> 401fffffffffffff + assertEquals(7.999999999999999, MathUtils.nextAfter(8.0, 2.142222222222222), 0.0); + + // 0xc020000000000000 0x400123456789abcd -> c01fffffffffffff + assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 2.142222222222222), 0.0); + + // 0x3f2e43753d36a223 0x3f2e43753d36a224 -> 3f2e43753d36a224 + assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.308922399667661E-4), 0.0); + + // 0x3f2e43753d36a223 0x3f2e43753d36a223 -> 3f2e43753d36a224 + assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676606E-4), 0.0); + + // 0x3f2e43753d36a223 0x3f2e43753d36a222 -> 3f2e43753d36a222 + assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676603E-4), 0.0); + + // 0x3f2e43753d36a223 0xbf2e43753d36a224 -> 3f2e43753d36a222 + assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.308922399667661E-4), 0.0); + + // 0x3f2e43753d36a223 0xbf2e43753d36a223 -> 3f2e43753d36a222 + assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676606E-4), 0.0); + + // 0x3f2e43753d36a223 0xbf2e43753d36a222 -> 3f2e43753d36a222 + assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676603E-4), 0.0); + + // 0xbf2e43753d36a223 0x3f2e43753d36a224 -> bf2e43753d36a222 + assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.308922399667661E-4), 0.0); + + // 0xbf2e43753d36a223 0x3f2e43753d36a223 -> bf2e43753d36a222 + assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676606E-4), 0.0); + + // 0xbf2e43753d36a223 0x3f2e43753d36a222 -> bf2e43753d36a222 + assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676603E-4), 0.0); + + // 0xbf2e43753d36a223 0xbf2e43753d36a224 -> bf2e43753d36a224 + assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.308922399667661E-4), 0.0); + + // 0xbf2e43753d36a223 0xbf2e43753d36a223 -> bf2e43753d36a224 + assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676606E-4), 0.0); + + // 0xbf2e43753d36a223 0xbf2e43753d36a222 -> bf2e43753d36a222 + assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676603E-4), 0.0); + + } + public void testRoundDouble() { double x = 1.234567890; assertEquals(1.23, MathUtils.round(x, 2), 0.0); assertEquals(1.235, MathUtils.round(x, 3), 0.0); assertEquals(1.2346, MathUtils.round(x, 4), 0.0); + // JIRA MATH-151 + assertEquals(39.25,MathUtils.round(39.245, 2), 0.0); + assertEquals(39.24,MathUtils.round(39.245, 2, + BigDecimal.ROUND_DOWN), 0.0); + double xx = 39.0; + xx = xx + 245d/1000d; + assertEquals(39.25,MathUtils.round(xx, 2), 0.0); + // BZ 35904 assertEquals(30.1d, MathUtils.round(30.095d, 2), 0.0d); assertEquals(30.1d, MathUtils.round(30.095d, 1), 0.0d); + assertEquals(33.1d, MathUtils.round(33.095d, 1), 0.0d); + assertEquals(33.1d, MathUtils.round(33.095d, 2), 0.0d); assertEquals(50.09d, MathUtils.round(50.085d, 2), 0.0d); assertEquals(50.19d, MathUtils.round(50.185d, 2), 0.0d); assertEquals(50.01d, MathUtils.round(50.005d, 2), 0.0d); @@ -671,7 +756,10 @@ } catch (IllegalArgumentException ex) { // success } - + + // MATH-151 + assertEquals(39.25, MathUtils.round(39.245, 2, BigDecimal.ROUND_HALF_UP), 0.0); + // special values TestUtils.assertEquals(Double.NaN, MathUtils.round(Double.NaN, 2), 0.0); assertEquals(0.0, MathUtils.round(0.0, 2), 0.0); Modified: jakarta/commons/proper/math/trunk/xdocs/changes.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/math/trunk/xdocs/changes.xml?rev=418934&r1=418933&r2=418934&view=diff ============================================================================== --- jakarta/commons/proper/math/trunk/xdocs/changes.xml (original) +++ jakarta/commons/proper/math/trunk/xdocs/changes.xml Tue Jul 4 01:09:25 2006 @@ -52,6 +52,13 @@ Modified ProperFractionFormat to reject embedded minus signs. + + Added a nextAfter method in MathUtils to return the next + machine-representable number in a specified direction from a given + floating point number. Used this to ensure that MathUtils.round does + not return incorrect results for numbers with bad IEEE754 + representations. +