diff --git a/src/main/java/org/apache/commons/math/util/MathUtils.java b/src/main/java/org/apache/commons/math/util/MathUtils.java index ce6b4d4e0..ae4e4869e 100644 --- a/src/main/java/org/apache/commons/math/util/MathUtils.java +++ b/src/main/java/org/apache/commons/math/util/MathUtils.java @@ -530,7 +530,7 @@ public final class MathUtils { * @deprecated This method considers that {@code NaN == NaN}. In release * 3.0, the semantics will change in order to comply with IEEE754 where it * is specified that {@code NaN != NaN}. - * New methods have been added for those cases wher the old semantics is + * New methods have been added for those cases where the old semantics is * useful (see e.g. {@link #equalsIncludingNaN(float[],float[]) * equalsIncludingNaN}. */ @@ -576,21 +576,22 @@ public final class MathUtils { } /** - * Returns true iff they are strictly equal. + * Returns true iff both arguments are NaN or neither is NaN and they are + * equal * + *
This method considers that {@code NaN == NaN}. In release + * 3.0, the semantics will change in order to comply with IEEE754 where it + * is specified that {@code NaN != NaN}. + * New methods have been added for those cases where the old semantics + * (w.r.t. NaN) is useful (see e.g. + * {@link #equalsIncludingNaN(double,double, double) equalsIncludingNaN}. + *
+ * * @param x first value * @param y second value * @return {@code true} if the values are equal. - * @deprecated This method considers that {@code NaN == NaN}. In release - * 3.0, the semantics will change in order to comply with IEEE754 where it - * is specified that {@code NaN != NaN}. Also, two adjacent floating point - * numbers will be considered equal. - * New methods have been added for those cases where the old semantics - * (w.r.t. NaN) is useful (see e.g. - * {@link #equalsIncludingNaN(double,double) equalsIncludingNaN}. */ - @Deprecated - public static boolean equals(double x, double y) { + public static boolean equals(double x, double y) { return (Double.isNaN(x) && Double.isNaN(y)) || x == y; } @@ -609,14 +610,23 @@ public final class MathUtils { /** * Returns true if both arguments are equal or within the range of allowed * error (inclusive). - * + *+ * Two NaNs are considered equals, as are two infinities with same sign. + *
+ *This method considers that {@code NaN == NaN}. In release + * 3.0, the semantics will change in order to comply with IEEE754 where it + * is specified that {@code NaN != NaN}. + * New methods have been added for those cases where the old semantics + * (w.r.t. NaN) is useful (see e.g. + * {@link #equalsIncludingNaN(double,double, double) equalsIncludingNaN}. + *
* @param x first value * @param y second value * @param eps the amount of absolute error to allow. * @return {@code true} if the values are equal or within range of each other. */ public static boolean equals(double x, double y, double eps) { - return equals(x, y, 1) || FastMath.abs(y - x) <= eps; + return equals(x, y) || FastMath.abs(y - x) <= eps; } /** @@ -643,6 +653,14 @@ public final class MathUtils { * href="http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm"> * Bruce Dawson * + *This method considers that {@code NaN == NaN}. In release + * 3.0, the semantics will change in order to comply with IEEE754 where it + * is specified that {@code NaN != NaN}. + * New methods have been added for those cases where the old semantics + * (w.r.t. NaN) is useful (see e.g. + * {@link #equalsIncludingNaN(double,double, int) equalsIncludingNaN}. + *
+ * * @param x first value * @param y second value * @param maxUlps {@code (maxUlps - 1)} is the number of floating point @@ -666,9 +684,7 @@ public final class MathUtils { yInt = SGN_MASK - yInt; } - final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps; - - return isEqual && !Double.isNaN(x) && !Double.isNaN(y); + return FastMath.abs(xInt - yInt) <= maxUlps; } /** @@ -691,18 +707,18 @@ public final class MathUtils { * their elements are equal as defined by * {@link #equals(double,double)}. * - * @param x first array - * @param y second array - * @return true if the values are both null or have same dimension - * and equal elements. - * @deprecated This method considers that {@code NaN == NaN}. In release + *This method considers that {@code NaN == NaN}. In release * 3.0, the semantics will change in order to comply with IEEE754 where it * is specified that {@code NaN != NaN}. * New methods have been added for those cases wher the old semantics is * useful (see e.g. {@link #equalsIncludingNaN(double[],double[]) * equalsIncludingNaN}. + *
+ * @param x first array + * @param y second array + * @return true if the values are both null or have same dimension + * and equal elements. */ - @Deprecated public static boolean equals(double[] x, double[] y) { if ((x == null) || (y == null)) { return !((x == null) ^ (y == null)); @@ -1290,10 +1306,46 @@ public final class MathUtils { * @return the next machine representable number in the specified direction * @since 1.2 * @deprecated as of 2.2, replaced by {@link FastMath#nextAfter(double, double)} + * which handles Infinities differently, and returns direction if d and direction compare equal. */ @Deprecated public static double nextAfter(double d, double direction) { - return FastMath.nextAfter(d, 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)); + } + } } /** diff --git a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java index 12e4c96c1..5335975f5 100644 --- a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java +++ b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java @@ -315,6 +315,26 @@ public final class MathUtilsTest extends TestCase { assertTrue(Double.isNaN(MathUtils.cosh(Double.NaN))); } + public void testEquals() { + double[] testArray = { + Double.NaN, + Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY, + 1d, + 0d }; + for (int i = 0; i < testArray.length; i++) { + for (int j = 0; j < testArray.length; j++) { + if (i == j) { + assertTrue(MathUtils.equals(testArray[i], testArray[j])); + assertTrue(MathUtils.equals(testArray[j], testArray[i])); + } else { + assertTrue(!MathUtils.equals(testArray[i], testArray[j])); + assertTrue(!MathUtils.equals(testArray[j], testArray[i])); + } + } + } + } + public void testEqualsIncludingNaN() { double[] testArray = { Double.NaN, @@ -339,7 +359,7 @@ public final class MathUtilsTest extends TestCase { assertTrue(MathUtils.equals(153.0000, 153.0000, .0625)); assertTrue(MathUtils.equals(153.0000, 153.0625, .0625)); assertTrue(MathUtils.equals(152.9375, 153.0000, .0625)); - assertFalse(MathUtils.equals(Double.NaN, Double.NaN, 1.0)); + assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1.0)); // This will change in 3.0 assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0)); assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0)); assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0)); @@ -394,6 +414,31 @@ public final class MathUtilsTest extends TestCase { assertFalse(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 100000)); } + public void testEqualsWithAllowedUlps21() { // From 2.1 + assertTrue(MathUtils.equals(153, 153, 1)); + + assertTrue(MathUtils.equals(153, 153.00000000000003, 1)); + assertFalse(MathUtils.equals(153, 153.00000000000006, 1)); + assertTrue(MathUtils.equals(153, 152.99999999999997, 1)); + assertFalse(MathUtils.equals(153, 152.99999999999994, 1)); + + assertTrue(MathUtils.equals(-128, -127.99999999999999, 1)); + assertFalse(MathUtils.equals(-128, -127.99999999999997, 1)); + assertTrue(MathUtils.equals(-128, -128.00000000000003, 1)); + assertFalse(MathUtils.equals(-128, -128.00000000000006, 1)); + + assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1)); + assertTrue(MathUtils.equals(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1)); + + assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1)); + assertTrue(MathUtils.equals(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1)); + + + assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1)); + + assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000)); + } + public void testEqualsWithAllowedUlps() { assertTrue(MathUtils.equals(0.0, -0.0, 1)); @@ -427,7 +472,7 @@ public final class MathUtilsTest extends TestCase { assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1)); assertTrue(MathUtils.equals(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1)); - assertFalse(MathUtils.equals(Double.NaN, Double.NaN, 1)); + assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1)); // This will change in 3.0 assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000)); } @@ -957,6 +1002,91 @@ public final class MathUtilsTest extends TestCase { } } + public void testNextAfter() { // Test from Math 2.1 + // 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 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 testScalb() { // Math 2.1 + assertEquals( 0.0, MathUtils.scalb(0.0, 5), 1.0e-15); + assertEquals(32.0, MathUtils.scalb(1.0, 5), 1.0e-15); + assertEquals(1.0 / 32.0, MathUtils.scalb(1.0, -5), 1.0e-15); + assertEquals(Math.PI, MathUtils.scalb(Math.PI, 0), 1.0e-15); + assertTrue(Double.isInfinite(MathUtils.scalb(Double.POSITIVE_INFINITY, 1))); + assertTrue(Double.isInfinite(MathUtils.scalb(Double.NEGATIVE_INFINITY, 1))); + assertTrue(Double.isNaN(MathUtils.scalb(Double.NaN, 1))); + } + public void testNormalizeAngle() { for (double a = -15.0; a <= 15.0; a += 0.1) { for (double b = -15.0; b <= 15.0; b += 0.2) { @@ -1436,6 +1566,38 @@ public final class MathUtilsTest extends TestCase { assertEquals(4, MathUtils.distanceInf(p1, p2)); } + public void testCheckOrder21() { // Test from 2.1 + MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 15}, 1, true); + MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 2}, 1, false); + MathUtils.checkOrder(new double[] {3, -5.5, -11, -27.5}, -1, true); + MathUtils.checkOrder(new double[] {3, 0, 0, -5.5, -11, -27.5}, -1, false); + + try { + MathUtils.checkOrder(new double[] {-15, -5.5, -1, -1, 2, 15}, 1, true); + fail("an exception should have been thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + MathUtils.checkOrder(new double[] {-15, -5.5, -1, -2, 2}, 1, false); + fail("an exception should have been thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + MathUtils.checkOrder(new double[] {3, 3, -5.5, -11, -27.5}, -1, true); + fail("an exception should have been thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + try { + MathUtils.checkOrder(new double[] {3, -1, 0, -5.5, -11, -27.5}, -1, false); + fail("an exception should have been thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + } + public void testCheckOrder() { MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 15}, MathUtils.OrderDirection.INCREASING, true);