You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by lu...@apache.org on 2014/06/13 16:20:05 UTC

svn commit: r1602438 - in /commons/proper/math/trunk/src: changes/changes.xml main/java/org/apache/commons/math3/util/Precision.java test/java/org/apache/commons/math3/util/PrecisionTest.java

Author: luc
Date: Fri Jun 13 14:20:05 2014
New Revision: 1602438

URL: http://svn.apache.org/r1602438
Log:
Fixed overflow in Precision.equals with ulps.

Both double and float versions were affected.

JIRA: MATH-1127

Modified:
    commons/proper/math/trunk/src/changes/changes.xml
    commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Precision.java
    commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/PrecisionTest.java

Modified: commons/proper/math/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/changes/changes.xml?rev=1602438&r1=1602437&r2=1602438&view=diff
==============================================================================
--- commons/proper/math/trunk/src/changes/changes.xml (original)
+++ commons/proper/math/trunk/src/changes/changes.xml Fri Jun 13 14:20:05 2014
@@ -73,6 +73,9 @@ Users are encouraged to upgrade to this 
   2. A few methods in the FastMath class are in fact slower that their
   counterpart in either Math or StrictMath (cf. MATH-740 and MATH-901).
 ">
+      <action dev="luc" type="fix" issue="MATH-1127">
+        Fixed overflow in Precision.equals with ulps (both double and float versions).
+      </action>
       <action dev="tn" type="fix" issue="MATH-1125" due-to="Ajo Fod">
         Performance improvements for Student's t-distribution.
       </action>

Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Precision.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Precision.java?rev=1602438&r1=1602437&r2=1602438&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Precision.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Precision.java Fri Jun 13 14:20:05 2014
@@ -62,6 +62,14 @@ public class Precision {
     private static final int SGN_MASK_FLOAT = 0x80000000;
     /** Positive zero. */
     private static final double POSITIVE_ZERO = 0d;
+    /** Positive zero bits. */
+    private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0);
+    /** Negative zero bits. */
+    private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0);
+    /** Positive zero bits. */
+    private static final int POSITIVE_ZERO_FLOAT_BITS   = Float.floatToRawIntBits(+0.0f);
+    /** Negative zero bits. */
+    private static final int NEGATIVE_ZERO_FLOAT_BITS   = Float.floatToRawIntBits(-0.0f);
 
     static {
         /*
@@ -109,7 +117,7 @@ public class Precision {
      * (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">
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
      * Bruce Dawson</a>
      *
      * @param x first value
@@ -190,7 +198,7 @@ public class Precision {
      * (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">
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
      * Bruce Dawson</a>
      *
      * @param x first value
@@ -201,21 +209,37 @@ public class Precision {
      * point values between {@code x} and {@code y}.
      * @since 2.2
      */
-    public static boolean equals(float x, float y, int maxUlps) {
-        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;
-        }
+    public static boolean equals(final float x, final float y, final int maxUlps) {
+
+        final int xInt = Float.floatToRawIntBits(x);
+        final int yInt = Float.floatToRawIntBits(y);
+
+        final boolean isEqual;
+        if (((xInt ^ yInt) & SGN_MASK_FLOAT) == 0) {
+            // number have same sign, there is no risk of overflow
+            isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+        } else {
+            // number have opposite signs, take care of overflow
+            final int deltaPlus;
+            final int deltaMinus;
+            if (xInt < yInt) {
+                deltaPlus  = yInt - POSITIVE_ZERO_FLOAT_BITS;
+                deltaMinus = xInt - NEGATIVE_ZERO_FLOAT_BITS;
+            } else {
+                deltaPlus  = xInt - POSITIVE_ZERO_FLOAT_BITS;
+                deltaMinus = yInt - NEGATIVE_ZERO_FLOAT_BITS;
+            }
 
-        final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+            if (deltaPlus > maxUlps) {
+                isEqual = false;
+            } else {
+                isEqual = deltaMinus <= (maxUlps - deltaPlus);
+            }
+
+        }
 
         return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
+
     }
 
     /**
@@ -315,12 +339,16 @@ public class Precision {
     /**
      * Returns true if both arguments are equal or within the range of allowed
      * error (inclusive).
+     * <p>
      * 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.
+     * (or fewer) floating point numbers between them, i.e. two adjacent
+     * floating point numbers are considered equal.
+     * </p>
+     * <p>
      * Adapted from <a
-     * href="http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm">
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
      * Bruce Dawson</a>
+     * </p>
      *
      * @param x first value
      * @param y second value
@@ -329,21 +357,37 @@ public class Precision {
      * @return {@code true} if there are fewer than {@code maxUlps} floating
      * point values between {@code x} and {@code y}.
      */
-    public static boolean equals(double x, double y, int maxUlps) {
-        long xInt = Double.doubleToLongBits(x);
-        long yInt = Double.doubleToLongBits(y);
-
-        // Make lexicographically ordered as a two's-complement integer.
-        if (xInt < 0) {
-            xInt = SGN_MASK - xInt;
-        }
-        if (yInt < 0) {
-            yInt = SGN_MASK - yInt;
-        }
+    public static boolean equals(final double x, final double y, final int maxUlps) {
+
+        final long xInt = Double.doubleToRawLongBits(x);
+        final long yInt = Double.doubleToRawLongBits(y);
+
+        final boolean isEqual;
+        if (((xInt ^ yInt) & SGN_MASK) == 0l) {
+            // number have same sign, there is no risk of overflow
+            isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+        } else {
+            // number have opposite signs, take care of overflow
+            final long deltaPlus;
+            final long deltaMinus;
+            if (xInt < yInt) {
+                deltaPlus  = yInt - POSITIVE_ZERO_DOUBLE_BITS;
+                deltaMinus = xInt - NEGATIVE_ZERO_DOUBLE_BITS;
+            } else {
+                deltaPlus  = xInt - POSITIVE_ZERO_DOUBLE_BITS;
+                deltaMinus = yInt - NEGATIVE_ZERO_DOUBLE_BITS;
+            }
+
+            if (deltaPlus > maxUlps) {
+                isEqual = false;
+            } else {
+                isEqual = deltaMinus <= (maxUlps - deltaPlus);
+            }
 
-        final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+        }
 
         return isEqual && !Double.isNaN(x) && !Double.isNaN(y);
+
     }
 
     /**

Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/PrecisionTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/PrecisionTest.java?rev=1602438&r1=1602437&r2=1602438&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/PrecisionTest.java (original)
+++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/PrecisionTest.java Fri Jun 13 14:20:05 2014
@@ -540,4 +540,13 @@ public class PrecisionTest {
         // b) 1 + "the number after EPSILON" is not equal to 1.
         Assert.assertFalse(1 + afterEpsilon == 1);
     }
+
+    @Test
+    public void testMath1127() {
+        Assert.assertFalse(Precision.equals(2.0, -2.0, 1));
+        Assert.assertTrue(Precision.equals(0.0, -0.0, 0));
+        Assert.assertFalse(Precision.equals(2.0f, -2.0f, 1));
+        Assert.assertTrue(Precision.equals(0.0f, -0.0f, 0));
+    }
+
 }