commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From s...@apache.org
Subject svn commit: r1060080 - in /commons/proper/math/branches/MATH_2_X/src: main/java/org/apache/commons/math/util/MathUtils.java site/xdoc/changes.xml test/java/org/apache/commons/math/util/MathUtilsTest.java
Date Mon, 17 Jan 2011 20:26:35 GMT
Author: sebb
Date: Mon Jan 17 20:26:34 2011
New Revision: 1060080

URL: http://svn.apache.org/viewvc?rev=1060080&view=rev
Log:
MATH-491 MathUtils.equals(double, double) does not work properly for floats
Added equals(float, float) methods and tests

Modified:
    commons/proper/math/branches/MATH_2_X/src/main/java/org/apache/commons/math/util/MathUtils.java
    commons/proper/math/branches/MATH_2_X/src/site/xdoc/changes.xml
    commons/proper/math/branches/MATH_2_X/src/test/java/org/apache/commons/math/util/MathUtilsTest.java

Modified: commons/proper/math/branches/MATH_2_X/src/main/java/org/apache/commons/math/util/MathUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/math/branches/MATH_2_X/src/main/java/org/apache/commons/math/util/MathUtils.java?rev=1060080&r1=1060079&r2=1060080&view=diff
==============================================================================
--- commons/proper/math/branches/MATH_2_X/src/main/java/org/apache/commons/math/util/MathUtils.java
(original)
+++ commons/proper/math/branches/MATH_2_X/src/main/java/org/apache/commons/math/util/MathUtils.java
Mon Jan 17 20:26:34 2011
@@ -71,6 +71,9 @@ public final class MathUtils {
     /** Offset to order signed double numbers lexicographically. */
     private static final long SGN_MASK = 0x8000000000000000L;
 
+    /** Offset to order signed double numbers lexicographically. */
+    private static final int SGN_MASK_FLOAT = 0x80000000;
+
     /** All long-representable factorials */
     private static final long[] FACTORIALS = new long[] {
                        1l,                  1l,                   2l,
@@ -405,6 +408,180 @@ public final class MathUtils {
         return (FastMath.exp(x) + FastMath.exp(-x)) / 2.0;
     }
 
+    ///////////////////////////////
+
+    /**
+     * Returns true iff if both arguments are NaN or neither is NaN
+     * and they are equal as defined by {x==y}.
+     *
+     * @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}.
+     * New methods have been added for those cases wher the old semantics is
+     * useful (see e.g. {@link #equalsIncludingNaN(float,float)
+     * equalsIncludingNaN}.
+     */
+    @Deprecated
+    public static boolean equals(float x, float y) {
+        return (Float.isNaN(x) && Float.isNaN(y)) || x == y;
+    }
+
+    /**
+     * Returns true if both arguments are NaN or neither is NaN and they are
+     * equal as defined by {@link #equals(float,float,int)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal or both are NaN.
+     */
+    public static boolean equalsIncludingNaN(float x, float y) {
+        return (Float.isNaN(x) && Float.isNaN(y)) || equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if both arguments are equal or within the range of allowed
+     * error (inclusive).
+     *
+     * @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(float x, float y, float eps) {
+        return equals(x, y, 1) || FastMath.abs(y - x) <= eps;
+    }
+
+    /**
+     * Returns true if both arguments are NaN or are equal or within the range
+     * of allowed error (inclusive).
+     *
+     * @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,
+     * or both are NaN.
+     */
+    public static boolean equalsIncludingNaN(float x, float y, float eps) {
+        return equalsIncludingNaN(x, y) || (FastMath.abs(y - x) <= eps);
+    }
+
+    /**
+     * Returns true if both arguments are equal or within the range of allowed
+     * error (inclusive).
+     * Two float numbers are considered equal if there are {@code (maxUlps - 1)}
+     * (or fewer) floating point numbers between them, i.e. two adjacent floating
+     * point numbers are considered equal.
+     * Adapted from <a
+     * href="http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm">
+     * Bruce Dawson</a>
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
+     * values between {@code x} and {@code y}.
+     * @return {@code true} if there are fewer than {@code maxUlps} floating
+     * point values between {@code x} and {@code y}.
+     */
+    public static boolean equals(float x, float y, int maxUlps) {
+        // Check that "maxUlps" is non-negative and small enough so that
+        // NaN won't compare as equal to anything (except another NaN).
+        assert maxUlps > 0 && maxUlps < NAN_GAP;
+
+        int xInt = Float.floatToIntBits(x);
+        int yInt = Float.floatToIntBits(y);
+
+        // Make lexicographically ordered as a two's-complement integer.
+        if (xInt < 0) {
+            xInt = SGN_MASK_FLOAT - xInt;
+        }
+        if (yInt < 0) {
+            yInt = SGN_MASK_FLOAT - yInt;
+        }
+
+        final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+
+        return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
+    }
+
+    /**
+     * Returns true if both arguments are NaN or if they are equal as defined
+     * by {@link #equals(float,float,int)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
+     * values between {@code x} and {@code y}.
+     * @return {@code true} if both arguments are NaN or if there are less than
+     * {@code maxUlps} floating point values between {@code x} and {@code y}.
+     */
+    public static boolean equalsIncludingNaN(float x, float y, int maxUlps) {
+        return (Float.isNaN(x) && Float.isNaN(y)) || equals(x, y, maxUlps);
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all
+     * their elements are equal as defined by
+     * {@link #equals(float,float)}.
+     *
+     * @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
+     * 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(float[],float[])
+     * equalsIncludingNaN}.
+     */
+    @Deprecated
+    public static boolean equals(float[] x, float[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!equals(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all
+     * their elements are equal as defined by
+     * {@link #equalsIncludingNaN(float,float)}.
+     *
+     * @param x first array
+     * @param y second array
+     * @return true if the values are both null or have same dimension and
+     * equal elements
+     */
+    public static boolean equalsIncludingNaN(float[] x, float[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!equalsIncludingNaN(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    //////////////////////////////////
+    
+    
     /**
      * Returns true iff they are equal as defined by
      * {@link #equals(double,double,int) this method}.
@@ -420,9 +597,9 @@ public final class MathUtils {
      * equalsIncludingNaN}.
      */
     @Deprecated
-    public static boolean equals(double x, double y) {
-        return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
-    }
+public static boolean equals(double x, double y) {
+    return (Double.isNaN(x) && Double.isNaN(y)) || x == y;
+}
 
     /**
      * Returns true if both arguments are NaN or neither is NaN and they are

Modified: commons/proper/math/branches/MATH_2_X/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/math/branches/MATH_2_X/src/site/xdoc/changes.xml?rev=1060080&r1=1060079&r2=1060080&view=diff
==============================================================================
--- commons/proper/math/branches/MATH_2_X/src/site/xdoc/changes.xml (original)
+++ commons/proper/math/branches/MATH_2_X/src/site/xdoc/changes.xml Mon Jan 17 20:26:34 2011
@@ -52,6 +52,10 @@ The <action> type attribute can be add,u
     If the output is not quite correct, check for invisible trailing spaces!
      -->
     <release version="2.2" date="TBD" description="TBD">
+      <action dev="sebb" type="fix" issue="MATH-491">
+        MATH-491 MathUtils.equals(double, double) does not work properly for floats
+        - add equivalent (float, float) methods and basic tests
+      </action>
       <action dev="sebb" type="fix" issue="MATH-482">
         FastMath.max(50.0f, -50.0f) => -50.0f; should be +50.0f
         Fixed FastMath.max(float, float) so it returns correct value.

Modified: commons/proper/math/branches/MATH_2_X/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/branches/MATH_2_X/src/test/java/org/apache/commons/math/util/MathUtilsTest.java?rev=1060080&r1=1060079&r2=1060080&view=diff
==============================================================================
--- commons/proper/math/branches/MATH_2_X/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
(original)
+++ commons/proper/math/branches/MATH_2_X/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
Mon Jan 17 20:26:34 2011
@@ -359,6 +359,40 @@ public final class MathUtilsTest extends
         assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
     }
 
+    // Tests for floating point equality
+    @SuppressWarnings("deprecation") // Math.equals(float, float)
+    public void testFloatEqualsWithAllowedUlps() {
+        assertTrue("+0.0f == -0.0f",MathUtils.equals(0.0f, -0.0f));
+        assertTrue("+0.0f == -0.0f (1 ulp)",MathUtils.equals(0.0f, -0.0f, 1));
+        float oneFloat = 1.0f;
+        assertTrue("1.0f == 1.0f + 1 ulp",MathUtils.equals(oneFloat, Float.intBitsToFloat(1
+ Float.floatToIntBits(oneFloat))));
+        assertTrue("1.0f == 1.0f + 1 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(1
+ Float.floatToIntBits(oneFloat)), 1));
+        assertFalse("1.0f != 1.0f + 2 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(2
+ Float.floatToIntBits(oneFloat)), 1));
+
+        assertTrue(MathUtils.equals(153.0f, 153.0f, 1));
+
+        // These tests need adjusting for floating point precision
+//        assertTrue(MathUtils.equals(153.0f, 153.00000000000003f, 1));
+//        assertFalse(MathUtils.equals(153.0f, 153.00000000000006f, 1));
+//        assertTrue(MathUtils.equals(153.0f, 152.99999999999997f, 1));
+//        assertFalse(MathUtils.equals(153f, 152.99999999999994f, 1));
+//
+//        assertTrue(MathUtils.equals(-128.0f, -127.99999999999999f, 1));
+//        assertFalse(MathUtils.equals(-128.0f, -127.99999999999997f, 1));
+//        assertTrue(MathUtils.equals(-128.0f, -128.00000000000003f, 1));
+//        assertFalse(MathUtils.equals(-128.0f, -128.00000000000006f, 1));
+
+        assertTrue(MathUtils.equals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(Double.MAX_VALUE, Float.POSITIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY, 1));
+
+        assertFalse(MathUtils.equals(Float.NaN, Float.NaN, 1));
+
+        assertFalse(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 100000));
+    }
+
     public void testEqualsWithAllowedUlps() {
         assertTrue(MathUtils.equals(0.0, -0.0, 1));
 
@@ -435,6 +469,7 @@ public final class MathUtilsTest extends
         assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
100000));
     }
 
+    @SuppressWarnings("deprecation") // specific tests of deprecated methods
     public void testArrayEquals() {
         assertFalse(MathUtils.equals(new double[] { 1d }, null));
         assertFalse(MathUtils.equals(null, new double[] { 1d }));
@@ -476,6 +511,26 @@ public final class MathUtilsTest extends
                                                  new double[] { FastMath.nextAfter(FastMath.nextAfter(1d,
2d), 2d) }));
     }
 
+    public void testFloatArrayEqualsIncludingNaN() {
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, null));
+        assertFalse(MathUtils.equalsIncludingNaN(null, new float[] { 1f }));
+        assertTrue(MathUtils.equalsIncludingNaN((float[]) null, (float[]) null));
+
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[0]));
+        assertTrue(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[] { 1f }));
+        assertTrue(MathUtils.equalsIncludingNaN(new float[] {
+                    Float.NaN, Float.POSITIVE_INFINITY,
+                    Float.NEGATIVE_INFINITY, 1f, 0f
+                }, new float[] {
+                Float.NaN, Float.POSITIVE_INFINITY,
+                Float.NEGATIVE_INFINITY, 1f, 0f
+                }));
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { Float.POSITIVE_INFINITY },
+                                                 new float[] { Float.NEGATIVE_INFINITY }));
+//        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f },
+//                                                 new float[] { FastMath.nextAfter(FastMath.nextAfter(1f,
2f), 2f) }));
+    }
+
     public void testFactorial() {
         for (int i = 1; i < 21; i++) {
             assertEquals(i + "! ", factorial(i), MathUtils.factorial(i));



Mime
View raw message