You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by se...@apache.org on 2011/01/13 03:57:57 UTC
svn commit: r1058397 - in /commons/proper/math/trunk/src:
main/java/org/apache/commons/math/util/MathUtils.java
test/java/org/apache/commons/math/util/MathUtilsTest.java
Author: sebb
Date: Thu Jan 13 02:57:57 2011
New Revision: 1058397
URL: http://svn.apache.org/viewvc?rev=1058397&view=rev
Log:
MATH-491 MathUtils.equals(double, double) does not work properly for floats
- add equivalent (float, float) methods and basic tests
Modified:
commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java
commons/proper/math/trunk/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java?rev=1058397&r1=1058396&r2=1058397&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java Thu Jan 13 02:57:57 2011
@@ -82,6 +82,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,
@@ -416,6 +419,160 @@ public final class MathUtils {
/**
* Returns true iff they are equal as defined by
+ * {@link #equals(float,float,int) equals(x, y, 1)}.
+ *
+ * @param x first value
+ * @param y second value
+ * @return {@code true} if the values are equal.
+ */
+ public static boolean equals(float x, float y) {
+ return equals(x, y, 1);
+ }
+
+ /**
+ * Returns true if both arguments are NaN or neither is NaN and they are
+ * equal as defined by {@link #equals(float,float) this method}.
+ *
+ * @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) this method}.
+ *
+ * @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) this method}.
+ *
+ * @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 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(double,double) this method}.
+ *
+ * @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) equals(x, y, 1)}.
*
* @param x first value
Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math/util/MathUtilsTest.java?rev=1058397&r1=1058396&r2=1058397&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/java/org/apache/commons/math/util/MathUtilsTest.java (original)
+++ commons/proper/math/trunk/src/test/java/org/apache/commons/math/util/MathUtilsTest.java Thu Jan 13 02:57:57 2011
@@ -363,6 +363,39 @@ public final class MathUtilsTest extends
assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
}
+ // Tests for floating point equality
+ 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));