You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2014/04/18 17:58:47 UTC
svn commit: r1588500 - in /commons/proper/math/trunk/src:
changes/changes.xml main/java/org/apache/commons/math3/complex/Complex.java
test/java/org/apache/commons/math3/complex/ComplexTest.java
Author: erans
Date: Fri Apr 18 15:58:47 2014
New Revision: 1588500
URL: http://svn.apache.org/r1588500
Log:
MATH-1118
Fixed compatibility of "equals(Object)" with "hashCode()" ("Complex" will
behave as JDK's "Double"). Added new methods for testing floating-point
equality.
Thanks to Cyrille Artho for reporting the issue.
Modified:
commons/proper/math/trunk/src/changes/changes.xml
commons/proper/math/trunk/src/main/java/org/apache/commons/math3/complex/Complex.java
commons/proper/math/trunk/src/test/java/org/apache/commons/math3/complex/ComplexTest.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=1588500&r1=1588499&r2=1588500&view=diff
==============================================================================
--- commons/proper/math/trunk/src/changes/changes.xml (original)
+++ commons/proper/math/trunk/src/changes/changes.xml Fri Apr 18 15:58:47 2014
@@ -51,6 +51,11 @@ If the output is not quite correct, chec
</properties>
<body>
<release version="3.3" date="TBD" description="TBD">
+ <action dev="erans" type="fix" issue="MATH-1118">
+ "Complex": Fixed compatibility of "equals(Object)" with "hashCode()".
+ Added new methods for testing floating-point equality between the real
+ (resp. imaginary) parts of two complex numbers.
+ </action>
<action dev="luc" type="update" >
Bracketing utility for univariate root solvers returns a tighter interval than before.
It also allows choosing the search interval expansion rate, supporting both linear
Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/complex/Complex.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/complex/Complex.java?rev=1588500&r1=1588499&r2=1588500&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/complex/Complex.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/complex/Complex.java Fri Apr 18 15:58:47 2014
@@ -27,6 +27,7 @@ import org.apache.commons.math3.exceptio
import org.apache.commons.math3.exception.util.LocalizedFormats;
import org.apache.commons.math3.util.FastMath;
import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
/**
* Representation of a Complex number, i.e. a number which has both a
@@ -321,19 +322,28 @@ public class Complex implements FieldEle
}
/**
- * Test for the equality of two Complex objects.
+ * Test for equality with another object.
* If both the real and imaginary parts of two complex numbers
* are exactly the same, and neither is {@code Double.NaN}, the two
* Complex objects are considered to be equal.
- * All {@code NaN} values are considered to be equal - i.e, if either
- * (or both) real and imaginary parts of the complex number are equal
- * to {@code Double.NaN}, the complex number is equal to
- * {@code NaN}.
+ * The behavior is the same as for JDK's {@link Double#equals(Object)
+ * Double}:
+ * <ul>
+ * <li>All {@code NaN} values are considered to be equal,
+ * i.e, if either (or both) real and imaginary parts of the complex
+ * number are equal to {@code Double.NaN}, the complex number is equal
+ * to {@code NaN}.
+ * </li>
+ * <li>
+ * Instances constructed with different representations of zero (i.e.
+ * either "0" or "-0") are <em>not</em> considered to be equal.
+ * </li>
+ * </ul>
*
- * @param other Object to test for equality to this
- * @return true if two Complex objects are equal, false if object is
- * {@code null}, not an instance of Complex, or not equal to this Complex
- * instance.
+ * @param other Object to test for equality with this instance.
+ * @return {@code true} if the objects are equal, {@code false} if object
+ * is {@code null}, not an instance of {@code Complex}, or not equal to
+ * this instance.
*/
@Override
public boolean equals(Object other) {
@@ -341,17 +351,95 @@ public class Complex implements FieldEle
return true;
}
if (other instanceof Complex){
- Complex c = (Complex)other;
+ Complex c = (Complex) other;
if (c.isNaN) {
return isNaN;
} else {
- return (real == c.real) && (imaginary == c.imaginary);
+ return MathUtils.equals(real, c.real) &&
+ MathUtils.equals(imaginary, c.imaginary);
}
}
return false;
}
/**
+ * Test for the floating-point equality between Complex objects.
+ * It returns {@code true} if both arguments are equal or within the
+ * range of allowed error (inclusive).
+ *
+ * @param x First value (cannot be {@code null}).
+ * @param y Second value (cannot be {@code null}).
+ * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
+ * values between the real (resp. imaginary) parts of {@code x} and
+ * {@code y}.
+ * @return {@code true} if there are fewer than {@code maxUlps} floating
+ * point values between the real (resp. imaginary) parts of {@code x}
+ * and {@code y}.
+ *
+ * @see Precision#equals(double,double,int)
+ * @since 3.3
+ */
+ public static boolean equals(Complex x, Complex y, int maxUlps) {
+ return Precision.equals(x.real, y.real, maxUlps) &&
+ Precision.equals(x.imaginary, y.imaginary, maxUlps);
+ }
+
+ /**
+ * Returns {@code true} iff the values are equal as defined by
+ * {@link #equals(Complex,Complex,int) equals(x, y, 1)}.
+ *
+ * @param x First value (cannot be {@code null}).
+ * @param y Second value (cannot be {@code null}).
+ * @return {@code true} if the values are equal.
+ *
+ * @since 3.3
+ */
+ public static boolean equals(Complex x, Complex y) {
+ return equals(x, y, 1);
+ }
+
+ /**
+ * Returns {@code true} if, both for the real part and for the imaginary
+ * part, there is no double value strictly between the arguments or the
+ * difference between them is within the range of allowed error
+ * (inclusive).
+ *
+ * @param x First value (cannot be {@code null}).
+ * @param y Second value (cannot be {@code null}).
+ * @param eps Amount of allowed absolute error.
+ * @return {@code true} if the values are two adjacent floating point
+ * numbers or they are within range of each other.
+ *
+ * @see Precision#equals(double,double,double)
+ * @since 3.3
+ */
+ public static boolean equals(Complex x, Complex y, double eps) {
+ return Precision.equals(x.real, y.real, eps) &&
+ Precision.equals(x.imaginary, y.imaginary, eps);
+ }
+
+ /**
+ * Returns {@code true} if, both for the real part and for the imaginary
+ * part, there is no double value strictly between the arguments or the
+ * relative difference between them is smaller or equal to the given
+ * tolerance.
+ *
+ * @param x First value (cannot be {@code null}).
+ * @param y Second value (cannot be {@code null}).
+ * @param eps Amount of allowed relative error.
+ * @return {@code true} if the values are two adjacent floating point
+ * numbers or they are within range of each other.
+ *
+ * @see Precision#equalsWithRelativeTolerance(double,double,double)
+ * @since 3.3
+ */
+ public static boolean equalsWithRelativeTolerance(Complex x, Complex y,
+ double eps) {
+ return Precision.equalsWithRelativeTolerance(x.real, y.real, eps) &&
+ Precision.equalsWithRelativeTolerance(x.imaginary, y.imaginary, eps);
+ }
+
+ /**
* Get a hashCode for the complex number.
* Any {@code Double.NaN} value in real or imaginary part produces
* the same hash code {@code 7}.
Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/complex/ComplexTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/complex/ComplexTest.java?rev=1588500&r1=1588499&r2=1588500&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/complex/ComplexTest.java (original)
+++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/complex/ComplexTest.java Fri Apr 18 15:58:47 2014
@@ -311,7 +311,7 @@ public class ComplexTest {
@Test
public void testReciprocalReal() {
Complex z = new Complex(-2.0, 0.0);
- Assert.assertEquals(new Complex(-0.5, 0.0), z.reciprocal());
+ Assert.assertTrue(Complex.equals(new Complex(-0.5, 0.0), z.reciprocal()));
}
@Test
@@ -497,6 +497,15 @@ public class ComplexTest {
Assert.assertFalse(x.equals(null));
}
+ @Test(expected=NullPointerException.class)
+ public void testFloatingPointEqualsPrecondition1() {
+ Complex.equals(new Complex(3.0, 4.0), null, 3);
+ }
+ @Test(expected=NullPointerException.class)
+ public void testFloatingPointEqualsPrecondition2() {
+ Complex.equals(null, new Complex(3.0, 4.0), 3);
+ }
+
@Test
public void testEqualsClass() {
Complex x = new Complex(3.0, 4.0);
@@ -510,6 +519,65 @@ public class ComplexTest {
}
@Test
+ public void testFloatingPointEquals() {
+ double re = -3.21;
+ double im = 456789e10;
+
+ final Complex x = new Complex(re, im);
+ Complex y = new Complex(re, im);
+
+ Assert.assertTrue(x.equals(y));
+ Assert.assertTrue(Complex.equals(x, y));
+
+ final int maxUlps = 5;
+ for (int i = 0; i < maxUlps; i++) {
+ re = Math.nextUp(re);
+ im = Math.nextUp(im);
+ }
+ y = new Complex(re, im);
+ Assert.assertTrue(Complex.equals(x, y, maxUlps));
+
+ re = Math.nextUp(re);
+ im = Math.nextUp(im);
+ y = new Complex(re, im);
+ Assert.assertFalse(Complex.equals(x, y, maxUlps));
+ }
+
+ @Test
+ public void testFloatingPointEqualsNaN() {
+ Complex c = new Complex(Double.NaN, 1);
+ Assert.assertFalse(Complex.equals(c, c));
+
+ c = new Complex(1, Double.NaN);
+ Assert.assertFalse(Complex.equals(c, c));
+ }
+
+ @Test
+ public void testFloatingPointEqualsWithAllowedDelta() {
+ final double re = 153.0000;
+ final double im = 152.9375;
+ final double tol1 = 0.0625;
+ final Complex x = new Complex(re, im);
+ final Complex y = new Complex(re + tol1, im + tol1);
+ Assert.assertTrue(Complex.equals(x, y, tol1));
+
+ final double tol2 = 0.0624;
+ Assert.assertFalse(Complex.equals(x, y, tol2));
+ }
+
+ @Test
+ public void testFloatingPointEqualsWithRelativeTolerance() {
+ final double tol = 1e-4;
+ final double re = 1;
+ final double im = 1e10;
+
+ final double f = 1 + tol;
+ final Complex x = new Complex(re, im);
+ final Complex y = new Complex(re * f, im * f);
+ Assert.assertTrue(Complex.equalsWithRelativeTolerance(x, y, tol));
+ }
+
+ @Test
public void testEqualsTrue() {
Complex x = new Complex(3.0, 4.0);
Complex y = new Complex(3.0, 4.0);
@@ -551,6 +619,21 @@ public class ComplexTest {
Complex imaginaryNaN = new Complex(0.0, Double.NaN);
Assert.assertEquals(realNaN.hashCode(), imaginaryNaN.hashCode());
Assert.assertEquals(imaginaryNaN.hashCode(), Complex.NaN.hashCode());
+
+ // MATH-1118
+ // "equals" and "hashCode" must be compatible: if two objects have
+ // different hash codes, "equals" must return false.
+ final String msg = "'equals' not compatible with 'hashCode'";
+
+ x = new Complex(0.0, 0.0);
+ y = new Complex(0.0, -0.0);
+ Assert.assertTrue(x.hashCode() != y.hashCode());
+ Assert.assertFalse(msg, x.equals(y));
+
+ x = new Complex(0.0, 0.0);
+ y = new Complex(-0.0, 0.0);
+ Assert.assertTrue(x.hashCode() != y.hashCode());
+ Assert.assertFalse(msg, x.equals(y));
}
@Test
@@ -1067,7 +1150,8 @@ public class ComplexTest {
/** test issue MATH-221 */
@Test
public void testMath221() {
- Assert.assertEquals(new Complex(0,-1), new Complex(0,1).multiply(new Complex(-1,0)));
+ Assert.assertTrue(Complex.equals(new Complex(0,-1),
+ new Complex(0,1).multiply(new Complex(-1,0))));
}
/**