You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2020/04/01 10:59:01 UTC
[commons-numbers] 07/08: Update ComplexTest to match the method
order of Complex.
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
commit b6be43eb887a76f95fe2527f6936f6fc382e6d0c
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Apr 1 11:36:39 2020 +0100
Update ComplexTest to match the method order of Complex.
Added note about the incomplete testing of ISO C99 math functions. These
are covered in other test classes.
---
.../commons/numbers/complex/ComplexTest.java | 1693 ++++++++++----------
1 file changed, 860 insertions(+), 833 deletions(-)
diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java
index 57b1228..1dcf513 100644
--- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java
+++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java
@@ -32,6 +32,16 @@ import org.junit.jupiter.api.Test;
/**
* Tests for {@link Complex}.
+ *
+ * <p>Note: The ISO C99 math functions are not fully tested in this class. See also:
+ *
+ * <ul>
+ * <li>{@link CStandardTest} for a test of the ISO C99 standards including special case handling.
+ * <li>{@link CReferenceTest} for a test of the output using standard finite value against an
+ * ISO C99 compliant reference implementation.
+ * <li>{@link ComplexEdgeCaseTest} for a test of extreme edge case finite values for real and/or
+ * imaginary parts that can create intermediate overflow or underflow.
+ * </ul>
*/
public class ComplexTest {
@@ -84,6 +94,38 @@ public class ComplexTest {
}
@Test
+ @Disabled("Used to output the java environment")
+ @SuppressWarnings("squid:S2699")
+ public void testJava() {
+ // CHECKSTYLE: stop Regexp
+ System.out.println(">>testJava()");
+ // MathTest#testExpSpecialCases() checks the following:
+ // Assert.assertEquals("exp of -infinity should be 0.0", 0.0,
+ // Math.exp(Double.NEGATIVE_INFINITY), Precision.EPSILON);
+ // Let's check how well Math works:
+ System.out.println("Math.exp=" + Math.exp(Double.NEGATIVE_INFINITY));
+ final String[] props = {"java.version", // Java Runtime Environment version
+ "java.vendor", // Java Runtime Environment vendor
+ "java.vm.specification.version", // Java Virtual Machine specification version
+ "java.vm.specification.vendor", // Java Virtual Machine specification vendor
+ "java.vm.specification.name", // Java Virtual Machine specification name
+ "java.vm.version", // Java Virtual Machine implementation version
+ "java.vm.vendor", // Java Virtual Machine implementation vendor
+ "java.vm.name", // Java Virtual Machine implementation name
+ "java.specification.version", // Java Runtime Environment specification
+ // version
+ "java.specification.vendor", // Java Runtime Environment specification vendor
+ "java.specification.name", // Java Runtime Environment specification name
+ "java.class.version", // Java class format version number
+ };
+ for (final String t : props) {
+ System.out.println(t + "=" + System.getProperty(t));
+ }
+ System.out.println("<<testJava()");
+ // CHECKSTYLE: resume Regexp
+ }
+
+ @Test
public void testCartesianConstructor() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(3.0, z.getReal());
@@ -126,7 +168,8 @@ public class ComplexTest {
@Test
public void testPolarConstructorAbsArg() {
- // The test should work with any seed but use a fixed seed to avoid build instability.
+ // The test should work with any seed but use a fixed seed to avoid build
+ // instability.
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64, 678678638L);
for (int i = 0; i < 10; i++) {
final double rho = rng.nextDouble();
@@ -147,6 +190,214 @@ public class ComplexTest {
Assertions.assertEquals(Math.sin(x), z.getImaginary());
}
+ /**
+ * Test parse and toString are compatible.
+ */
+ @Test
+ public void testParseAndToString() {
+ final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1, Math.PI, Double.POSITIVE_INFINITY,
+ Double.NaN};
+ for (final double x : parts) {
+ for (final double y : parts) {
+ final Complex z = Complex.ofCartesian(x, y);
+ Assertions.assertEquals(z, Complex.parse(z.toString()));
+ }
+ }
+ final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+ for (int i = 0; i < 10; i++) {
+ final double x = -1 + rng.nextDouble() * 2;
+ final double y = -1 + rng.nextDouble() * 2;
+ final Complex z = Complex.ofCartesian(x, y);
+ Assertions.assertEquals(z, Complex.parse(z.toString()));
+ }
+
+ // Special values not covered
+ Assertions.assertEquals(Complex.ofPolar(2, pi), Complex.parse(Complex.ofPolar(2, pi).toString()));
+ Assertions.assertEquals(Complex.ofCis(pi), Complex.parse(Complex.ofCis(pi).toString()));
+ }
+
+ @Test
+ public void testParseNull() {
+ Assertions.assertThrows(NullPointerException.class, () -> Complex.parse(null));
+ }
+
+ @Test
+ public void testParseEmpty() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(""));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(" "));
+ }
+
+ @Test
+ public void testParseWrongStart() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("1.0,2.0)"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("[1.0,2.0)"));
+ }
+
+ @Test
+ public void testParseWrongEnd() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0]"));
+ }
+
+ @Test
+ public void testParseWrongSeparator() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0 2.0)"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0:2.0)"));
+ }
+
+ @Test
+ public void testParseSeparatorOutsideStartAndEnd() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0),"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(",(1.0,2.0)"));
+ }
+
+ @Test
+ public void testParseExtraSeparator() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,,2.0)"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0,)"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(,1.0,2.0)"));
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2,0)"));
+ }
+
+ @Test
+ public void testParseInvalidRe() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(I.0,2.0)"));
+ }
+
+ @Test
+ public void testParseInvalidIm() {
+ Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.G)"));
+ }
+
+ @Test
+ public void testParseSpaceAllowedAroundNumbers() {
+ final double re = 1.234;
+ final double im = 5.678;
+ final Complex z = Complex.ofCartesian(re, im);
+ Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + ")"));
+ Assertions.assertEquals(z, Complex.parse("( " + re + "," + im + ")"));
+ Assertions.assertEquals(z, Complex.parse("(" + re + " ," + im + ")"));
+ Assertions.assertEquals(z, Complex.parse("(" + re + ", " + im + ")"));
+ Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + " )"));
+ Assertions.assertEquals(z, Complex.parse("( " + re + " , " + im + " )"));
+ }
+
+ @Test
+ public void testCGrammar() {
+ final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+ for (int i = 0; i < 10; i++) {
+ final Complex z = Complex.ofCartesian(rng.nextDouble(), rng.nextDouble());
+ Assertions.assertEquals(z.getReal(), z.real(), "real");
+ Assertions.assertEquals(z.getImaginary(), z.imag(), "imag");
+ }
+ }
+
+ @Test
+ public void testAbs() {
+ final Complex z = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertEquals(5.0, z.abs());
+ }
+
+ @Test
+ public void testAbsNaN() {
+ // The result is NaN if either argument is NaN and the other is not infinite
+ Assertions.assertEquals(nan, NAN.abs());
+ Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).abs());
+ Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).abs());
+ // The result is positive infinite if either argument is infinite
+ Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(inf, 3.0).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(-inf, 3.0).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(3.0, inf).abs());
+ Assertions.assertEquals(inf, Complex.ofCartesian(3.0, -inf).abs());
+ }
+
+ /**
+ * Test standard values
+ */
+ @Test
+ public void testArg() {
+ Complex z = Complex.ofCartesian(1, 0);
+ assertArgument(0.0, z, 1.0e-12);
+
+ z = Complex.ofCartesian(1, 1);
+ assertArgument(Math.PI / 4, z, 1.0e-12);
+
+ z = Complex.ofCartesian(0, 1);
+ assertArgument(Math.PI / 2, z, 1.0e-12);
+
+ z = Complex.ofCartesian(-1, 1);
+ assertArgument(3 * Math.PI / 4, z, 1.0e-12);
+
+ z = Complex.ofCartesian(-1, 0);
+ assertArgument(Math.PI, z, 1.0e-12);
+
+ z = Complex.ofCartesian(-1, -1);
+ assertArgument(-3 * Math.PI / 4, z, 1.0e-12);
+
+ z = Complex.ofCartesian(0, -1);
+ assertArgument(-Math.PI / 2, z, 1.0e-12);
+
+ z = Complex.ofCartesian(1, -1);
+ assertArgument(-Math.PI / 4, z, 1.0e-12);
+ }
+
+ /**
+ * Verify atan2-style handling of infinite parts
+ */
+ @Test
+ public void testArgInf() {
+ assertArgument(Math.PI / 4, infInf, 1.0e-12);
+ assertArgument(Math.PI / 2, oneInf, 1.0e-12);
+ assertArgument(0.0, infOne, 1.0e-12);
+ assertArgument(Math.PI / 2, zeroInf, 1.0e-12);
+ assertArgument(0.0, infZero, 1.0e-12);
+ assertArgument(Math.PI, negInfOne, 1.0e-12);
+ assertArgument(-3.0 * Math.PI / 4, negInfNegInf, 1.0e-12);
+ assertArgument(-Math.PI / 2, oneNegInf, 1.0e-12);
+ }
+
+ /**
+ * Verify that either part NaN results in NaN
+ */
+ @Test
+ public void testArgNaN() {
+ assertArgument(Double.NaN, nanZero, 0);
+ assertArgument(Double.NaN, zeroNan, 0);
+ assertArgument(Double.NaN, NAN, 0);
+ }
+
+ private static void assertArgument(double expected, Complex complex, double delta) {
+ final double actual = complex.arg();
+ Assertions.assertEquals(expected, actual, delta);
+ Assertions.assertEquals(actual, complex.arg(), delta);
+ }
+
+ @Test
+ public void testNorm() {
+ final Complex z = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertEquals(25.0, z.norm());
+ }
+
+ @Test
+ public void testNormNaN() {
+ // The result is NaN if either argument is NaN and the other is not infinite
+ Assertions.assertEquals(nan, NAN.norm());
+ Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).norm());
+ Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).norm());
+ // The result is positive infinite if either argument is infinite
+ Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).norm());
+ Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).norm());
+ Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).norm());
+ Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).norm());
+ }
+
+ /**
+ * Test all number types: isNaN, isInfinite, isFinite.
+ */
@Test
public void testNumberType() {
assertNumberType(0, 0, NumberType.FINITE);
@@ -206,6 +457,42 @@ public class ComplexTest {
}
@Test
+ public void testConjugate() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex z = x.conj();
+ Assertions.assertEquals(3.0, z.getReal());
+ Assertions.assertEquals(-4.0, z.getImaginary());
+ }
+
+ @Test
+ public void testConjugateNaN() {
+ final Complex z = NAN.conj();
+ Assertions.assertTrue(z.isNaN());
+ }
+
+ @Test
+ public void testConjugateInfinite() {
+ Complex z = Complex.ofCartesian(0, inf);
+ Assertions.assertEquals(neginf, z.conj().getImaginary());
+ z = Complex.ofCartesian(0, neginf);
+ Assertions.assertEquals(inf, z.conj().getImaginary());
+ }
+
+ @Test
+ public void testNegate() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex z = x.negate();
+ Assertions.assertEquals(-3.0, z.getReal());
+ Assertions.assertEquals(-4.0, z.getImaginary());
+ }
+
+ @Test
+ public void testNegateNaN() {
+ final Complex z = NAN.negate();
+ Assertions.assertTrue(z.isNaN());
+ }
+
+ @Test
public void testProj() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertSame(z, z.proj());
@@ -222,48 +509,6 @@ public class ComplexTest {
}
@Test
- public void testAbs() {
- final Complex z = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertEquals(5.0, z.abs());
- }
-
- @Test
- public void testAbsNaN() {
- // The result is NaN if either argument is NaN and the other is not infinite
- Assertions.assertEquals(nan, NAN.abs());
- Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).abs());
- Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).abs());
- // The result is positive infinite if either argument is infinite
- Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(inf, 3.0).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(-inf, 3.0).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(3.0, inf).abs());
- Assertions.assertEquals(inf, Complex.ofCartesian(3.0, -inf).abs());
- }
-
- @Test
- public void testNorm() {
- final Complex z = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertEquals(25.0, z.norm());
- }
-
- @Test
- public void testNormNaN() {
- // The result is NaN if either argument is NaN and the other is not infinite
- Assertions.assertEquals(nan, NAN.norm());
- Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).norm());
- Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).norm());
- // The result is positive infinite if either argument is infinite
- Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).norm());
- Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).norm());
- Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).norm());
- Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).norm());
- }
-
- @Test
public void testAdd() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(5.0, 6.0);
@@ -377,198 +622,203 @@ public class ComplexTest {
}
@Test
- public void testConjugate() {
+ public void testSubtract() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex z = x.conj();
- Assertions.assertEquals(3.0, z.getReal());
- Assertions.assertEquals(-4.0, z.getImaginary());
+ final Complex y = Complex.ofCartesian(5.0, 7.0);
+ final Complex z = x.subtract(y);
+ Assertions.assertEquals(-2.0, z.getReal());
+ Assertions.assertEquals(-3.0, z.getImaginary());
}
@Test
- public void testConjugateNaN() {
- final Complex z = NAN.conj();
- Assertions.assertTrue(z.isNaN());
- }
+ public void testSubtractInf() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex y = Complex.ofCartesian(inf, 7.0);
+ Complex z = x.subtract(y);
+ Assertions.assertEquals(neginf, z.getReal());
+ Assertions.assertEquals(-3.0, z.getImaginary());
- @Test
- public void testConjugateInfinite() {
- Complex z = Complex.ofCartesian(0, inf);
- Assertions.assertEquals(neginf, z.conj().getImaginary());
- z = Complex.ofCartesian(0, neginf);
- Assertions.assertEquals(inf, z.conj().getImaginary());
+ z = y.subtract(y);
+ Assertions.assertEquals(nan, z.getReal());
+ Assertions.assertEquals(0.0, z.getImaginary());
}
@Test
- public void testDivide() {
+ public void testSubtractReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex y = Complex.ofCartesian(5.0, 6.0);
- final Complex z = x.divide(y);
- Assertions.assertEquals(39.0 / 61.0, z.getReal());
- Assertions.assertEquals(2.0 / 61.0, z.getImaginary());
+ final double y = 5.0;
+ final Complex z = x.subtract(y);
+ Assertions.assertEquals(-2.0, z.getReal());
+ Assertions.assertEquals(4.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
- public void testDivideZero() {
+ public void testSubtractRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex z = x.divide(Complex.ZERO);
- Assertions.assertEquals(INF, z);
- }
-
- @Test
- public void testDivideZeroZero() {
- final Complex x = Complex.ofCartesian(0.0, 0.0);
- final Complex z = x.divide(Complex.ZERO);
- Assertions.assertEquals(NAN, z);
+ final double y = nan;
+ final Complex z = x.subtract(y);
+ Assertions.assertEquals(nan, z.getReal());
+ Assertions.assertEquals(4.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
- public void testDivideNaN() {
+ public void testSubtractRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex z = x.divide(NAN);
- Assertions.assertTrue(z.isNaN());
+ final double y = inf;
+ final Complex z = x.subtract(y);
+ Assertions.assertEquals(-inf, z.getReal());
+ Assertions.assertEquals(4.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
- public void testDivideNanInf() {
- Complex z = oneInf.divide(Complex.ONE);
- Assertions.assertTrue(Double.isNaN(z.getReal()));
- Assertions.assertEquals(inf, z.getImaginary(), 0);
-
- z = negInfNegInf.divide(oneNan);
- Assertions.assertTrue(Double.isNaN(z.getReal()));
- Assertions.assertTrue(Double.isNaN(z.getImaginary()));
-
- z = negInfInf.divide(Complex.ONE);
- Assertions.assertTrue(Double.isInfinite(z.getReal()));
- Assertions.assertTrue(Double.isInfinite(z.getImaginary()));
+ public void testSubtractRealWithNegZeroImaginary() {
+ final Complex x = Complex.ofCartesian(3.0, -0.0);
+ final double y = 5.0;
+ final Complex z = x.subtract(y);
+ Assertions.assertEquals(-2.0, z.getReal());
+ Assertions.assertEquals(-0.0, z.getImaginary());
+ // Equivalent
+ // Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
+ Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
- public void testDivideReal() {
+ public void testSubtractImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 2.0;
- Complex z = x.divide(y);
- Assertions.assertEquals(1.5, z.getReal());
- Assertions.assertEquals(2.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(y)));
-
- z = x.divide(-y);
- Assertions.assertEquals(-1.5, z.getReal());
- Assertions.assertEquals(-2.0, z.getImaginary());
+ final double y = 5.0;
+ final Complex z = x.subtractImaginary(y);
+ Assertions.assertEquals(3.0, z.getReal());
+ Assertions.assertEquals(-1.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
- public void testDivideRealNaN() {
+ public void testSubtractImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
- final Complex z = x.divide(y);
- Assertions.assertEquals(nan, z.getReal());
+ final Complex z = x.subtractImaginary(y);
+ Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(y)));
+ Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
- public void testDivideRealInf() {
+ public void testSubtractImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
- Complex z = x.divide(y);
- Assertions.assertEquals(0.0, z.getReal());
- Assertions.assertEquals(0.0, z.getImaginary());
+ final Complex z = x.subtractImaginary(y);
+ Assertions.assertEquals(3.0, z.getReal());
+ Assertions.assertEquals(-inf, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(y)));
+ Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
+ }
- z = x.divide(-y);
+ @Test
+ public void testSubtractImaginaryWithNegZeroReal() {
+ final Complex x = Complex.ofCartesian(-0.0, 4.0);
+ final double y = 5.0;
+ final Complex z = x.subtractImaginary(y);
Assertions.assertEquals(-0.0, z.getReal());
- Assertions.assertEquals(-0.0, z.getImaginary());
+ Assertions.assertEquals(-1.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ // Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
+ Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
- public void testDivideRealZero() {
+ public void testSubtractFromReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 0.0;
- Complex z = x.divide(y);
- Assertions.assertEquals(inf, z.getReal());
- Assertions.assertEquals(inf, z.getImaginary());
+ final double y = 5.0;
+ final Complex z = x.subtractFrom(y);
+ Assertions.assertEquals(2.0, z.getReal());
+ Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(y)));
+ Assertions.assertEquals(z, ofReal(y).subtract(x));
+ }
- z = x.divide(-y);
- Assertions.assertEquals(-inf, z.getReal());
- Assertions.assertEquals(-inf, z.getImaginary());
+ @Test
+ public void testSubtractFromRealNaN() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = nan;
+ final Complex z = x.subtractFrom(y);
+ Assertions.assertEquals(nan, z.getReal());
+ Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ Assertions.assertEquals(z, ofReal(y).subtract(x));
}
@Test
- public void testDivideImaginary() {
+ public void testSubtractFromRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 2.0;
- Complex z = x.divideImaginary(y);
- Assertions.assertEquals(2.0, z.getReal());
- Assertions.assertEquals(-1.5, z.getImaginary());
+ final double y = inf;
+ final Complex z = x.subtractFrom(y);
+ Assertions.assertEquals(inf, z.getReal());
+ Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofImaginary(y)));
+ Assertions.assertEquals(z, ofReal(y).subtract(x));
+ }
- z = x.divideImaginary(-y);
- Assertions.assertEquals(-2.0, z.getReal());
- Assertions.assertEquals(1.5, z.getImaginary());
+ @Test
+ public void testSubtractFromRealWithPosZeroImaginary() {
+ final Complex x = Complex.ofCartesian(3.0, 0.0);
+ final double y = 5.0;
+ final Complex z = x.subtractFrom(y);
+ Assertions.assertEquals(2.0, z.getReal());
+ Assertions.assertEquals(-0.0, z.getImaginary(), "Expected sign inversion");
+ // Sign-inversion is a problem: 0.0 - 0.0 == 0.0
+ Assertions.assertNotEquals(z, ofReal(y).subtract(x));
+ }
+
+ @Test
+ public void testSubtractFromImaginary() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = 5.0;
+ final Complex z = x.subtractFromImaginary(y);
+ Assertions.assertEquals(-3.0, z.getReal());
+ Assertions.assertEquals(1.0, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
+ Assertions.assertEquals(z, ofImaginary(y).subtract(x));
}
@Test
- public void testDivideImaginaryNaN() {
+ public void testSubtractFromImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
- final Complex z = x.divideImaginary(y);
- Assertions.assertEquals(nan, z.getReal());
+ final Complex z = x.subtractFromImaginary(y);
+ Assertions.assertEquals(-3.0, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofImaginary(y)));
+ Assertions.assertEquals(z, ofImaginary(y).subtract(x));
}
@Test
- public void testDivideImaginaryInf() {
+ public void testSubtractFromImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
- Complex z = x.divideImaginary(y);
- Assertions.assertEquals(0.0, z.getReal());
- Assertions.assertEquals(-0.0, z.getImaginary());
+ final Complex z = x.subtractFromImaginary(y);
+ Assertions.assertEquals(-3.0, z.getReal());
+ Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
- Assertions.assertEquals(z, x.divide(ofImaginary(y)));
-
- z = x.divideImaginary(-y);
- Assertions.assertEquals(-0.0, z.getReal());
- Assertions.assertEquals(0.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
- }
+ Assertions.assertEquals(z, ofImaginary(y).subtract(x));
+ }
@Test
- public void testDivideImaginaryZero() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 0.0;
- Complex z = x.divideImaginary(y);
- Assertions.assertEquals(inf, z.getReal());
- Assertions.assertEquals(-inf, z.getImaginary());
- // Sign-preservation is a problem for imaginary: 0.0 - -0.0 == 0.0
- Complex z2 = x.divide(ofImaginary(y));
- Assertions.assertEquals(inf, z2.getReal());
- Assertions.assertEquals(inf, z2.getImaginary(), "Expected no sign preservation");
-
- z = x.divideImaginary(-y);
- Assertions.assertEquals(-inf, z.getReal());
- Assertions.assertEquals(inf, z.getImaginary());
- // Sign-preservation is a problem for real: 0.0 + -0.0 == 0.0
- z2 = x.divide(ofImaginary(-y));
- Assertions.assertEquals(inf, z2.getReal(), "Expected no sign preservation");
- Assertions.assertEquals(inf, z2.getImaginary());
+ public void testSubtractFromImaginaryWithPosZeroReal() {
+ final Complex x = Complex.ofCartesian(0.0, 4.0);
+ final double y = 5.0;
+ final Complex z = x.subtractFromImaginary(y);
+ Assertions.assertEquals(-0.0, z.getReal(), "Expected sign inversion");
+ Assertions.assertEquals(1.0, z.getImaginary());
+ // Sign-inversion is a problem: 0.0 - 0.0 == 0.0
+ Assertions.assertNotEquals(z, ofImaginary(y).subtract(x));
}
@Test
@@ -760,7 +1010,7 @@ public class ComplexTest {
}
@Test
- public void testZeroMultiplyI() {
+ public void testMultiplyZeroByI() {
final double[] zeros = {-0.0, 0.0};
for (final double a : zeros) {
for (final double b : zeros) {
@@ -785,7 +1035,7 @@ public class ComplexTest {
}
@Test
- public void testZeroMultiplyNegativeI() {
+ public void testMultiplyZeroByNegativeI() {
// Depending on how we represent -I this does not work for 2/4 cases
// but the cases are different. Here we test the negation of I.
final Complex negI = Complex.I.negate();
@@ -821,8 +1071,181 @@ public class ComplexTest {
}
}
+ @Test
+ public void testDivide() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex y = Complex.ofCartesian(5.0, 6.0);
+ final Complex z = x.divide(y);
+ Assertions.assertEquals(39.0 / 61.0, z.getReal());
+ Assertions.assertEquals(2.0 / 61.0, z.getImaginary());
+ }
+
+ @Test
+ public void testDivideZero() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex z = x.divide(Complex.ZERO);
+ Assertions.assertEquals(INF, z);
+ }
+
+ @Test
+ public void testDivideZeroZero() {
+ final Complex x = Complex.ofCartesian(0.0, 0.0);
+ final Complex z = x.divide(Complex.ZERO);
+ Assertions.assertEquals(NAN, z);
+ }
+
+ @Test
+ public void testDivideNaN() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex z = x.divide(NAN);
+ Assertions.assertTrue(z.isNaN());
+ }
+
+ @Test
+ public void testDivideNanInf() {
+ Complex z = oneInf.divide(Complex.ONE);
+ Assertions.assertTrue(Double.isNaN(z.getReal()));
+ Assertions.assertEquals(inf, z.getImaginary(), 0);
+
+ z = negInfNegInf.divide(oneNan);
+ Assertions.assertTrue(Double.isNaN(z.getReal()));
+ Assertions.assertTrue(Double.isNaN(z.getImaginary()));
+
+ z = negInfInf.divide(Complex.ONE);
+ Assertions.assertTrue(Double.isInfinite(z.getReal()));
+ Assertions.assertTrue(Double.isInfinite(z.getImaginary()));
+ }
+
+ @Test
+ public void testDivideReal() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = 2.0;
+ Complex z = x.divide(y);
+ Assertions.assertEquals(1.5, z.getReal());
+ Assertions.assertEquals(2.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(y)));
+
+ z = x.divide(-y);
+ Assertions.assertEquals(-1.5, z.getReal());
+ Assertions.assertEquals(-2.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ }
+
+ @Test
+ public void testDivideRealNaN() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = nan;
+ final Complex z = x.divide(y);
+ Assertions.assertEquals(nan, z.getReal());
+ Assertions.assertEquals(nan, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(y)));
+ }
+
+ @Test
+ public void testDivideRealInf() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = inf;
+ Complex z = x.divide(y);
+ Assertions.assertEquals(0.0, z.getReal());
+ Assertions.assertEquals(0.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(y)));
+
+ z = x.divide(-y);
+ Assertions.assertEquals(-0.0, z.getReal());
+ Assertions.assertEquals(-0.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ }
+
+ @Test
+ public void testDivideRealZero() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = 0.0;
+ Complex z = x.divide(y);
+ Assertions.assertEquals(inf, z.getReal());
+ Assertions.assertEquals(inf, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(y)));
+
+ z = x.divide(-y);
+ Assertions.assertEquals(-inf, z.getReal());
+ Assertions.assertEquals(-inf, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofReal(-y)));
+ }
+
+ @Test
+ public void testDivideImaginary() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = 2.0;
+ Complex z = x.divideImaginary(y);
+ Assertions.assertEquals(2.0, z.getReal());
+ Assertions.assertEquals(-1.5, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofImaginary(y)));
+
+ z = x.divideImaginary(-y);
+ Assertions.assertEquals(-2.0, z.getReal());
+ Assertions.assertEquals(1.5, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
+ }
+
+ @Test
+ public void testDivideImaginaryNaN() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = nan;
+ final Complex z = x.divideImaginary(y);
+ Assertions.assertEquals(nan, z.getReal());
+ Assertions.assertEquals(nan, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofImaginary(y)));
+ }
+
+ @Test
+ public void testDivideImaginaryInf() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = inf;
+ Complex z = x.divideImaginary(y);
+ Assertions.assertEquals(0.0, z.getReal());
+ Assertions.assertEquals(-0.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofImaginary(y)));
+
+ z = x.divideImaginary(-y);
+ Assertions.assertEquals(-0.0, z.getReal());
+ Assertions.assertEquals(0.0, z.getImaginary());
+ // Equivalent
+ Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
+ }
+
+ @Test
+ public void testDivideImaginaryZero() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final double y = 0.0;
+ Complex z = x.divideImaginary(y);
+ Assertions.assertEquals(inf, z.getReal());
+ Assertions.assertEquals(-inf, z.getImaginary());
+ // Sign-preservation is a problem for imaginary: 0.0 - -0.0 == 0.0
+ Complex z2 = x.divide(ofImaginary(y));
+ Assertions.assertEquals(inf, z2.getReal());
+ Assertions.assertEquals(inf, z2.getImaginary(), "Expected no sign preservation");
+
+ z = x.divideImaginary(-y);
+ Assertions.assertEquals(-inf, z.getReal());
+ Assertions.assertEquals(inf, z.getImaginary());
+ // Sign-preservation is a problem for real: 0.0 + -0.0 == 0.0
+ z2 = x.divide(ofImaginary(-y));
+ Assertions.assertEquals(inf, z2.getReal(), "Expected no sign preservation");
+ Assertions.assertEquals(inf, z2.getImaginary());
+ }
+
/**
- * Arithmetic test using combinations of +/- x for real, imaginary and and the double
+ * Arithmetic test using combinations of +/- x for real, imaginary and the double
* argument for add, subtract, subtractFrom, multiply and divide, where x is zero or
* non-zero.
*
@@ -939,7 +1362,7 @@ public class ComplexTest {
* by zero using a Complex then multiplied by I.
*/
@Test
- public void testDivideImaginaryArithmetic() {
+ public void testSignedDivideImaginaryArithmetic() {
// Cases for divide by non-zero:
// 2: (-0.0,+x) / -y
// 4: (+x,+/-0.0) / -/+y
@@ -991,497 +1414,37 @@ public class ComplexTest {
}
@Test
- public void testNegate() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex z = x.negate();
- Assertions.assertEquals(-3.0, z.getReal());
- Assertions.assertEquals(-4.0, z.getImaginary());
+ public void testLog10() {
+ final double ln10 = Math.log(10);
+ final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+ for (int i = 0; i < 10; i++) {
+ final Complex z = Complex.ofCartesian(rng.nextDouble() * 2, rng.nextDouble() * 2);
+ final Complex lnz = z.log();
+ final Complex log10z = z.log10();
+ // This is prone to floating-point error so use a delta
+ Assertions.assertEquals(lnz.getReal() / ln10, log10z.getReal(), 1e-12, "real");
+ // This test should be exact
+ Assertions.assertEquals(lnz.getImaginary(), log10z.getImaginary(), "imag");
+ }
}
@Test
- public void testNegateNaN() {
- final Complex z = NAN.negate();
- Assertions.assertTrue(z.isNaN());
+ public void testPow() {
+ final Complex x = Complex.ofCartesian(3, 4);
+ final double yDouble = 5.0;
+ final Complex yComplex = ofReal(yDouble);
+ Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble));
}
@Test
- public void testSubtract() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex y = Complex.ofCartesian(5.0, 7.0);
- final Complex z = x.subtract(y);
- Assertions.assertEquals(-2.0, z.getReal());
- Assertions.assertEquals(-3.0, z.getImaginary());
- }
-
- @Test
- public void testSubtractInf() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex y = Complex.ofCartesian(inf, 7.0);
- Complex z = x.subtract(y);
- Assertions.assertEquals(neginf, z.getReal());
- Assertions.assertEquals(-3.0, z.getImaginary());
-
- z = y.subtract(y);
- Assertions.assertEquals(nan, z.getReal());
- Assertions.assertEquals(0.0, z.getImaginary());
- }
-
- @Test
- public void testSubtractReal() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtract(y);
- Assertions.assertEquals(-2.0, z.getReal());
- Assertions.assertEquals(4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofReal(y)));
- }
-
- @Test
- public void testSubtractRealNaN() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = nan;
- final Complex z = x.subtract(y);
- Assertions.assertEquals(nan, z.getReal());
- Assertions.assertEquals(4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofReal(y)));
- }
-
- @Test
- public void testSubtractRealInf() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = inf;
- final Complex z = x.subtract(y);
- Assertions.assertEquals(-inf, z.getReal());
- Assertions.assertEquals(4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofReal(y)));
- }
-
- @Test
- public void testSubtractRealWithNegZeroImaginary() {
- final Complex x = Complex.ofCartesian(3.0, -0.0);
- final double y = 5.0;
- final Complex z = x.subtract(y);
- Assertions.assertEquals(-2.0, z.getReal());
- Assertions.assertEquals(-0.0, z.getImaginary());
- // Equivalent
- // Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
- Assertions.assertEquals(z, x.subtract(ofReal(y)));
- }
-
- @Test
- public void testSubtractImaginary() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtractImaginary(y);
- Assertions.assertEquals(3.0, z.getReal());
- Assertions.assertEquals(-1.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
- }
-
- @Test
- public void testSubtractImaginaryNaN() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = nan;
- final Complex z = x.subtractImaginary(y);
- Assertions.assertEquals(3.0, z.getReal());
- Assertions.assertEquals(nan, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
- }
-
- @Test
- public void testSubtractImaginaryInf() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = inf;
- final Complex z = x.subtractImaginary(y);
- Assertions.assertEquals(3.0, z.getReal());
- Assertions.assertEquals(-inf, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
- }
-
- @Test
- public void testSubtractImaginaryWithNegZeroReal() {
- final Complex x = Complex.ofCartesian(-0.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtractImaginary(y);
- Assertions.assertEquals(-0.0, z.getReal());
- Assertions.assertEquals(-1.0, z.getImaginary());
- // Equivalent
- // Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
- Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
- }
-
- @Test
- public void testSubtractFromReal() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtractFrom(y);
- Assertions.assertEquals(2.0, z.getReal());
- Assertions.assertEquals(-4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofReal(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromRealNaN() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = nan;
- final Complex z = x.subtractFrom(y);
- Assertions.assertEquals(nan, z.getReal());
- Assertions.assertEquals(-4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofReal(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromRealInf() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = inf;
- final Complex z = x.subtractFrom(y);
- Assertions.assertEquals(inf, z.getReal());
- Assertions.assertEquals(-4.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofReal(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromRealWithPosZeroImaginary() {
- final Complex x = Complex.ofCartesian(3.0, 0.0);
- final double y = 5.0;
- final Complex z = x.subtractFrom(y);
- Assertions.assertEquals(2.0, z.getReal());
- Assertions.assertEquals(-0.0, z.getImaginary(), "Expected sign inversion");
- // Sign-inversion is a problem: 0.0 - 0.0 == 0.0
- Assertions.assertNotEquals(z, ofReal(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromImaginary() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtractFromImaginary(y);
- Assertions.assertEquals(-3.0, z.getReal());
- Assertions.assertEquals(1.0, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofImaginary(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromImaginaryNaN() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = nan;
- final Complex z = x.subtractFromImaginary(y);
- Assertions.assertEquals(-3.0, z.getReal());
- Assertions.assertEquals(nan, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofImaginary(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromImaginaryInf() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final double y = inf;
- final Complex z = x.subtractFromImaginary(y);
- Assertions.assertEquals(-3.0, z.getReal());
- Assertions.assertEquals(inf, z.getImaginary());
- // Equivalent
- Assertions.assertEquals(z, ofImaginary(y).subtract(x));
- }
-
- @Test
- public void testSubtractFromImaginaryWithPosZeroReal() {
- final Complex x = Complex.ofCartesian(0.0, 4.0);
- final double y = 5.0;
- final Complex z = x.subtractFromImaginary(y);
- Assertions.assertEquals(-0.0, z.getReal(), "Expected sign inversion");
- Assertions.assertEquals(1.0, z.getImaginary());
- // Sign-inversion is a problem: 0.0 - 0.0 == 0.0
- Assertions.assertNotEquals(z, ofImaginary(y).subtract(x));
- }
-
- @Test
- public void testEqualsWithNull() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertFalse(x.equals(null));
- }
-
- @Test
- public void testEqualsWithAnotherClass() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertFalse(x.equals(new Object()));
- }
-
- @Test
- public void testEqualsWithSameObject() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertEquals(x, x);
- }
-
- @Test
- public void testEqualsWithCopyObject() {
- final Complex x = Complex.ofCartesian(3.0, 4.0);
- final Complex y = Complex.ofCartesian(3.0, 4.0);
- Assertions.assertEquals(x, y);
- }
-
- @Test
- public void testEqualsWithRealDifference() {
- final Complex x = Complex.ofCartesian(0.0, 0.0);
- final Complex y = Complex.ofCartesian(0.0 + Double.MIN_VALUE, 0.0);
- Assertions.assertNotEquals(x, y);
- }
-
- @Test
- public void testEqualsWithImaginaryDifference() {
- final Complex x = Complex.ofCartesian(0.0, 0.0);
- final Complex y = Complex.ofCartesian(0.0, 0.0 + Double.MIN_VALUE);
- Assertions.assertNotEquals(x, y);
- }
-
- /**
- * Test {@link Complex#equals(Object)}. It should be consistent with
- * {@link Arrays#equals(double[], double[])} called using the components of two
- * complex numbers.
- */
- @Test
- public void testEqualsIsConsistentWithArraysEquals() {
- // Explicit check of the cases documented in the Javadoc:
- assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(Double.NaN, 0.0),
- Complex.ofCartesian(Double.NaN, 1.0), "NaN real and different non-NaN imaginary");
- assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, Double.NaN),
- Complex.ofCartesian(1.0, Double.NaN), "Different non-NaN real and NaN imaginary");
- assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(-0.0, 0.0),
- "Different real zeros");
- assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(0.0, -0.0),
- "Different imaginary zeros");
-
- // Test some values of edge cases
- final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, -1, 0, 1};
- final ArrayList<Complex> list = createCombinations(values);
-
- for (final Complex c : list) {
- final double real = c.getReal();
- final double imag = c.getImaginary();
-
- // Check a copy is equal
- assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imag), "Copy complex");
-
- // Perform the smallest change to the two components
- final double realDelta = smallestChange(real);
- final double imagDelta = smallestChange(imag);
- Assertions.assertNotEquals(real, realDelta, "Real was not changed");
- Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
-
- assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(realDelta, imag), "Delta real");
- assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imagDelta), "Delta imaginary");
- }
- }
-
- /**
- * Specific test to target different representations that return {@code true} for
- * {@link Complex#isNaN()} are {@code false} for {@link Complex#equals(Object)}.
- */
- @Test
- public void testEqualsWithDifferentNaNs() {
- // Test some NaN combinations
- final double[] values = {Double.NaN, 0, 1};
- final ArrayList<Complex> list = createCombinations(values);
-
- // Is the all-vs-all comparison only the exact same values should be equal, e.g.
- // (nan,0) not equals (nan,nan)
- // (nan,0) equals (nan,0)
- // (nan,0) not equals (0,nan)
- for (int i = 0; i < list.size(); i++) {
- final Complex c1 = list.get(i);
- final Complex copy = Complex.ofCartesian(c1.getReal(), c1.getImaginary());
- assertEqualsIsConsistentWithArraysEquals(c1, copy, "Copy is not equal");
- for (int j = i + 1; j < list.size(); j++) {
- final Complex c2 = list.get(j);
- assertEqualsIsConsistentWithArraysEquals(c1, c2, "Different NaNs should not be equal");
- }
- }
- }
-
- /**
- * Test the two complex numbers with {@link Complex#equals(Object)} and check the
- * result is consistent with {@link Arrays#equals(double[], double[])}.
- *
- * @param c1 the first complex
- * @param c2 the second complex
- * @param msg the message to append to an assertion error
- */
- private static void assertEqualsIsConsistentWithArraysEquals(Complex c1, Complex c2, String msg) {
- final boolean expected = Arrays.equals(new double[] {c1.getReal(), c1.getImaginary()},
- new double[] {c2.getReal(), c2.getImaginary()});
- final boolean actual = c1.equals(c2);
- Assertions.assertEquals(expected, actual,
- () -> String.format("equals(Object) is not consistent with Arrays.equals: %s. %s vs %s", msg, c1, c2));
- }
-
- /**
- * Test {@link Complex#hashCode()}. It should be consistent with
- * {@link Arrays#hashCode(double[])} called using the components of the complex number
- * and fulfil the contract of {@link Object#hashCode()}, i.e. objects with different
- * hash codes are {@code false} for {@link Object#equals(Object)}.
- */
- @Test
- public void testHashCode() {
- // Test some values match Arrays.hashCode(double[])
- final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, -3.45, -1, -0.0, 0.0, Double.MIN_VALUE, 1, 3.45,
- Double.POSITIVE_INFINITY};
- final ArrayList<Complex> list = createCombinations(values);
-
- final String msg = "'equals' not compatible with 'hashCode'";
-
- for (final Complex c : list) {
- final double real = c.getReal();
- final double imag = c.getImaginary();
- final int expected = Arrays.hashCode(new double[] {real, imag});
- final int hash = c.hashCode();
- Assertions.assertEquals(expected, hash, "hashCode does not match Arrays.hashCode({re, im})");
-
- // Test a copy has the same hash code, i.e. is not
- // System.identityHashCode(Object)
- final Complex copy = Complex.ofCartesian(real, imag);
- Assertions.assertEquals(hash, copy.hashCode(), "Copy hash code is not equal");
-
- // MATH-1118
- // "equals" and "hashCode" must be compatible: if two objects have
- // different hash codes, "equals" must return false.
- // Perform the smallest change to the two components.
- // Note: The hash could actually be the same so we check it changes.
- final double realDelta = smallestChange(real);
- final double imagDelta = smallestChange(imag);
- Assertions.assertNotEquals(real, realDelta, "Real was not changed");
- Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
-
- final Complex cRealDelta = Complex.ofCartesian(realDelta, imag);
- final Complex cImagDelta = Complex.ofCartesian(real, imagDelta);
- if (hash != cRealDelta.hashCode()) {
- Assertions.assertNotEquals(c, cRealDelta, () -> "real+delta: " + msg);
- }
- if (hash != cImagDelta.hashCode()) {
- Assertions.assertNotEquals(c, cImagDelta, () -> "imaginary+delta: " + msg);
- }
- }
- }
-
- /**
- * Specific test that different representations of zero satisfy the contract of
- * {@link Object#hashCode()}: if two objects have different hash codes, "equals" must
- * return false. This is an issue with using {@link Double#hashCode(double)} to create
- * hash codes and {@code ==} for equality when using different representations of
- * zero: Double.hashCode(-0.0) != Double.hashCode(0.0) but -0.0 == 0.0 is
- * {@code true}.
- *
- * @see <a
- * href="https://issues.apache.org/jira/projects/MATH/issues/MATH-1118">MATH-1118</a>
- */
- @Test
- public void testHashCodeWithDifferentZeros() {
- final double[] values = {-0.0, 0.0};
- final ArrayList<Complex> list = createCombinations(values);
-
- // Explicit test for issue MATH-1118
- // "equals" and "hashCode" must be compatible
- for (int i = 0; i < list.size(); i++) {
- final Complex c1 = list.get(i);
- for (int j = i + 1; j < list.size(); j++) {
- final Complex c2 = list.get(j);
- if (c1.hashCode() != c2.hashCode()) {
- Assertions.assertNotEquals(c1, c2, "'equals' not compatible with 'hashCode'");
- }
- }
- }
- }
-
- /**
- * Creates a list of Complex numbers using an all-vs-all combination of the provided
- * values for both the real and imaginary parts.
- *
- * @param values the values
- * @return the list
- */
- private static ArrayList<Complex> createCombinations(final double[] values) {
- final ArrayList<Complex> list = new ArrayList<>(values.length * values.length);
- for (final double re : values) {
- for (final double im : values) {
- list.add(Complex.ofCartesian(re, im));
- }
- }
- return list;
- }
-
- /**
- * Perform the smallest change to the value. This returns the next double value
- * adjacent to d in the direction of infinity. Edge cases: if already infinity then
- * return the next closest in the direction of negative infinity; if nan then return
- * 0.
- *
- * @param x the x
- * @return the new value
- */
- private static double smallestChange(double x) {
- if (Double.isNaN(x)) {
- return 0;
- }
- return x == Double.POSITIVE_INFINITY ? Math.nextDown(x) : Math.nextUp(x);
- }
-
- @Test
- @Disabled("Used to output the java environment")
- @SuppressWarnings("squid:S2699")
- public void testJava() {
- // CHECKSTYLE: stop Regexp
- System.out.println(">>testJava()");
- // MathTest#testExpSpecialCases() checks the following:
- // Assert.assertEquals("exp of -infinity should be 0.0", 0.0,
- // Math.exp(Double.NEGATIVE_INFINITY), Precision.EPSILON);
- // Let's check how well Math works:
- System.out.println("Math.exp=" + Math.exp(Double.NEGATIVE_INFINITY));
- final String[] props = {"java.version", // Java Runtime Environment version
- "java.vendor", // Java Runtime Environment vendor
- "java.vm.specification.version", // Java Virtual Machine specification version
- "java.vm.specification.vendor", // Java Virtual Machine specification vendor
- "java.vm.specification.name", // Java Virtual Machine specification name
- "java.vm.version", // Java Virtual Machine implementation version
- "java.vm.vendor", // Java Virtual Machine implementation vendor
- "java.vm.name", // Java Virtual Machine implementation name
- "java.specification.version", // Java Runtime Environment specification
- // version
- "java.specification.vendor", // Java Runtime Environment specification vendor
- "java.specification.name", // Java Runtime Environment specification name
- "java.class.version", // Java class format version number
- };
- for (final String t : props) {
- System.out.println(t + "=" + System.getProperty(t));
- }
- System.out.println("<<testJava()");
- // CHECKSTYLE: resume Regexp
- }
-
- @Test
- public void testPow() {
- final Complex x = Complex.ofCartesian(3, 4);
- final double yDouble = 5.0;
- final Complex yComplex = ofReal(yDouble);
- Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble));
- }
-
- @Test
- public void testPowComplexRealZero() {
- // Hits the edge case when real == 0 but imaginary != 0
- final Complex x = Complex.ofCartesian(0, 1);
- final Complex z = Complex.ofCartesian(2, 3);
- final Complex c = x.pow(z);
- // Answer from g++
- Assertions.assertEquals(-0.008983291021129429, c.getReal());
- Assertions.assertEquals(1.1001358594835313e-18, c.getImaginary());
+ public void testPowComplexRealZero() {
+ // Hits the edge case when real == 0 but imaginary != 0
+ final Complex x = Complex.ofCartesian(0, 1);
+ final Complex z = Complex.ofCartesian(2, 3);
+ final Complex c = x.pow(z);
+ // Answer from g++
+ Assertions.assertEquals(-0.008983291021129429, c.getReal());
+ Assertions.assertEquals(1.1001358594835313e-18, c.getImaginary());
}
@Test
@@ -1746,191 +1709,245 @@ public class ComplexTest {
Assertions.assertEquals(n, r.size());
for (final Complex c : r) {
Assertions.assertTrue(Double.isNaN(c.getReal()));
- Assertions.assertTrue(Double.isNaN(c.getImaginary()));
- }
- }
-
- @Test
- public void testNthRootInf() {
- final int n = 3;
- final Complex z = ofReal(Double.NEGATIVE_INFINITY);
- final List<Complex> r = z.nthRoot(n);
- Assertions.assertEquals(n, r.size());
- }
-
- /**
- * Test standard values
- */
- @Test
- public void testGetArgument() {
- Complex z = Complex.ofCartesian(1, 0);
- assertGetArgument(0.0, z, 1.0e-12);
-
- z = Complex.ofCartesian(1, 1);
- assertGetArgument(Math.PI / 4, z, 1.0e-12);
-
- z = Complex.ofCartesian(0, 1);
- assertGetArgument(Math.PI / 2, z, 1.0e-12);
-
- z = Complex.ofCartesian(-1, 1);
- assertGetArgument(3 * Math.PI / 4, z, 1.0e-12);
-
- z = Complex.ofCartesian(-1, 0);
- assertGetArgument(Math.PI, z, 1.0e-12);
-
- z = Complex.ofCartesian(-1, -1);
- assertGetArgument(-3 * Math.PI / 4, z, 1.0e-12);
-
- z = Complex.ofCartesian(0, -1);
- assertGetArgument(-Math.PI / 2, z, 1.0e-12);
-
- z = Complex.ofCartesian(1, -1);
- assertGetArgument(-Math.PI / 4, z, 1.0e-12);
- }
-
- /**
- * Verify atan2-style handling of infinite parts
- */
- @Test
- public void testGetArgumentInf() {
- assertGetArgument(Math.PI / 4, infInf, 1.0e-12);
- assertGetArgument(Math.PI / 2, oneInf, 1.0e-12);
- assertGetArgument(0.0, infOne, 1.0e-12);
- assertGetArgument(Math.PI / 2, zeroInf, 1.0e-12);
- assertGetArgument(0.0, infZero, 1.0e-12);
- assertGetArgument(Math.PI, negInfOne, 1.0e-12);
- assertGetArgument(-3.0 * Math.PI / 4, negInfNegInf, 1.0e-12);
- assertGetArgument(-Math.PI / 2, oneNegInf, 1.0e-12);
- }
-
- /**
- * Verify that either part NaN results in NaN
- */
- @Test
- public void testGetArgumentNaN() {
- assertGetArgument(Double.NaN, nanZero, 0);
- assertGetArgument(Double.NaN, zeroNan, 0);
- assertGetArgument(Double.NaN, NAN, 0);
- }
-
- private static void assertGetArgument(double expected, Complex complex, double delta) {
- final double actual = complex.arg();
- Assertions.assertEquals(expected, actual, delta);
- Assertions.assertEquals(actual, complex.arg(), delta);
- }
-
- @Test
- public void testParse() {
- final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1, Math.PI, Double.POSITIVE_INFINITY,
- Double.NaN};
- for (final double x : parts) {
- for (final double y : parts) {
- final Complex z = Complex.ofCartesian(x, y);
- Assertions.assertEquals(z, Complex.parse(z.toString()));
- }
- }
- final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
- for (int i = 0; i < 10; i++) {
- final double x = -1 + rng.nextDouble() * 2;
- final double y = -1 + rng.nextDouble() * 2;
- final Complex z = Complex.ofCartesian(x, y);
- Assertions.assertEquals(z, Complex.parse(z.toString()));
- }
-
- // Special values not covered
- Assertions.assertEquals(Complex.ofPolar(2, pi), Complex.parse(Complex.ofPolar(2, pi).toString()));
- Assertions.assertEquals(Complex.ofCis(pi), Complex.parse(Complex.ofCis(pi).toString()));
+ Assertions.assertTrue(Double.isNaN(c.getImaginary()));
+ }
}
@Test
- public void testParseNull() {
- Assertions.assertThrows(NullPointerException.class, () -> Complex.parse(null));
+ public void testNthRootInf() {
+ final int n = 3;
+ final Complex z = ofReal(Double.NEGATIVE_INFINITY);
+ final List<Complex> r = z.nthRoot(n);
+ Assertions.assertEquals(n, r.size());
}
@Test
- public void testParseEmpty() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(""));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(" "));
+ public void testEqualsWithNull() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertFalse(x.equals(null));
}
@Test
- public void testParseWrongStart() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("1.0,2.0)"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("[1.0,2.0)"));
+ public void testEqualsWithAnotherClass() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertFalse(x.equals(new Object()));
}
@Test
- public void testParseWrongEnd() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0]"));
+ public void testEqualsWithSameObject() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertEquals(x, x);
}
@Test
- public void testParseWrongSeparator() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0 2.0)"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0:2.0)"));
+ public void testEqualsWithCopyObject() {
+ final Complex x = Complex.ofCartesian(3.0, 4.0);
+ final Complex y = Complex.ofCartesian(3.0, 4.0);
+ Assertions.assertEquals(x, y);
}
@Test
- public void testParseSeparatorOutsideStartAndEnd() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0),"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(",(1.0,2.0)"));
+ public void testEqualsWithRealDifference() {
+ final Complex x = Complex.ofCartesian(0.0, 0.0);
+ final Complex y = Complex.ofCartesian(0.0 + Double.MIN_VALUE, 0.0);
+ Assertions.assertNotEquals(x, y);
}
@Test
- public void testParseExtraSeparator() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,,2.0)"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0,)"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(,1.0,2.0)"));
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2,0)"));
+ public void testEqualsWithImaginaryDifference() {
+ final Complex x = Complex.ofCartesian(0.0, 0.0);
+ final Complex y = Complex.ofCartesian(0.0, 0.0 + Double.MIN_VALUE);
+ Assertions.assertNotEquals(x, y);
}
+ /**
+ * Test {@link Complex#equals(Object)}. It should be consistent with
+ * {@link Arrays#equals(double[], double[])} called using the components of two
+ * complex numbers.
+ */
@Test
- public void testParseInvalidRe() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(I.0,2.0)"));
+ public void testEqualsIsConsistentWithArraysEquals() {
+ // Explicit check of the cases documented in the Javadoc:
+ assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(Double.NaN, 0.0),
+ Complex.ofCartesian(Double.NaN, 1.0), "NaN real and different non-NaN imaginary");
+ assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, Double.NaN),
+ Complex.ofCartesian(1.0, Double.NaN), "Different non-NaN real and NaN imaginary");
+ assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(-0.0, 0.0),
+ "Different real zeros");
+ assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(0.0, -0.0),
+ "Different imaginary zeros");
+
+ // Test some values of edge cases
+ final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, -1, 0, 1};
+ final ArrayList<Complex> list = createCombinations(values);
+
+ for (final Complex c : list) {
+ final double real = c.getReal();
+ final double imag = c.getImaginary();
+
+ // Check a copy is equal
+ assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imag), "Copy complex");
+
+ // Perform the smallest change to the two components
+ final double realDelta = smallestChange(real);
+ final double imagDelta = smallestChange(imag);
+ Assertions.assertNotEquals(real, realDelta, "Real was not changed");
+ Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
+
+ assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(realDelta, imag), "Delta real");
+ assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imagDelta), "Delta imaginary");
+ }
}
+ /**
+ * Specific test to target different representations that return {@code true} for
+ * {@link Complex#isNaN()} are {@code false} for {@link Complex#equals(Object)}.
+ */
@Test
- public void testParseInvalidIm() {
- Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.G)"));
+ public void testEqualsWithDifferentNaNs() {
+ // Test some NaN combinations
+ final double[] values = {Double.NaN, 0, 1};
+ final ArrayList<Complex> list = createCombinations(values);
+
+ // Is the all-vs-all comparison only the exact same values should be equal, e.g.
+ // (nan,0) not equals (nan,nan)
+ // (nan,0) equals (nan,0)
+ // (nan,0) not equals (0,nan)
+ for (int i = 0; i < list.size(); i++) {
+ final Complex c1 = list.get(i);
+ final Complex copy = Complex.ofCartesian(c1.getReal(), c1.getImaginary());
+ assertEqualsIsConsistentWithArraysEquals(c1, copy, "Copy is not equal");
+ for (int j = i + 1; j < list.size(); j++) {
+ final Complex c2 = list.get(j);
+ assertEqualsIsConsistentWithArraysEquals(c1, c2, "Different NaNs should not be equal");
+ }
+ }
}
- @Test
- public void testParseSpaceAllowedAroundNumbers() {
- final double re = 1.234;
- final double im = 5.678;
- final Complex z = Complex.ofCartesian(re, im);
- Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + ")"));
- Assertions.assertEquals(z, Complex.parse("( " + re + "," + im + ")"));
- Assertions.assertEquals(z, Complex.parse("(" + re + " ," + im + ")"));
- Assertions.assertEquals(z, Complex.parse("(" + re + ", " + im + ")"));
- Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + " )"));
- Assertions.assertEquals(z, Complex.parse("( " + re + " , " + im + " )"));
+ /**
+ * Test the two complex numbers with {@link Complex#equals(Object)} and check the
+ * result is consistent with {@link Arrays#equals(double[], double[])}.
+ *
+ * @param c1 the first complex
+ * @param c2 the second complex
+ * @param msg the message to append to an assertion error
+ */
+ private static void assertEqualsIsConsistentWithArraysEquals(Complex c1, Complex c2, String msg) {
+ final boolean expected = Arrays.equals(new double[] {c1.getReal(), c1.getImaginary()},
+ new double[] {c2.getReal(), c2.getImaginary()});
+ final boolean actual = c1.equals(c2);
+ Assertions.assertEquals(expected, actual,
+ () -> String.format("equals(Object) is not consistent with Arrays.equals: %s. %s vs %s", msg, c1, c2));
}
+ /**
+ * Test {@link Complex#hashCode()}. It should be consistent with
+ * {@link Arrays#hashCode(double[])} called using the components of the complex number
+ * and fulfil the contract of {@link Object#hashCode()}, i.e. objects with different
+ * hash codes are {@code false} for {@link Object#equals(Object)}.
+ */
@Test
- public void testCGrammar() {
- final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
- for (int i = 0; i < 10; i++) {
- final Complex z = Complex.ofCartesian(rng.nextDouble(), rng.nextDouble());
- Assertions.assertEquals(z.getReal(), z.real(), "real");
- Assertions.assertEquals(z.getImaginary(), z.imag(), "imag");
+ public void testHashCode() {
+ // Test some values match Arrays.hashCode(double[])
+ final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, -3.45, -1, -0.0, 0.0, Double.MIN_VALUE, 1, 3.45,
+ Double.POSITIVE_INFINITY};
+ final ArrayList<Complex> list = createCombinations(values);
+
+ final String msg = "'equals' not compatible with 'hashCode'";
+
+ for (final Complex c : list) {
+ final double real = c.getReal();
+ final double imag = c.getImaginary();
+ final int expected = Arrays.hashCode(new double[] {real, imag});
+ final int hash = c.hashCode();
+ Assertions.assertEquals(expected, hash, "hashCode does not match Arrays.hashCode({re, im})");
+
+ // Test a copy has the same hash code, i.e. is not
+ // System.identityHashCode(Object)
+ final Complex copy = Complex.ofCartesian(real, imag);
+ Assertions.assertEquals(hash, copy.hashCode(), "Copy hash code is not equal");
+
+ // MATH-1118
+ // "equals" and "hashCode" must be compatible: if two objects have
+ // different hash codes, "equals" must return false.
+ // Perform the smallest change to the two components.
+ // Note: The hash could actually be the same so we check it changes.
+ final double realDelta = smallestChange(real);
+ final double imagDelta = smallestChange(imag);
+ Assertions.assertNotEquals(real, realDelta, "Real was not changed");
+ Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
+
+ final Complex cRealDelta = Complex.ofCartesian(realDelta, imag);
+ final Complex cImagDelta = Complex.ofCartesian(real, imagDelta);
+ if (hash != cRealDelta.hashCode()) {
+ Assertions.assertNotEquals(c, cRealDelta, () -> "real+delta: " + msg);
+ }
+ if (hash != cImagDelta.hashCode()) {
+ Assertions.assertNotEquals(c, cImagDelta, () -> "imaginary+delta: " + msg);
+ }
}
}
+ /**
+ * Specific test that different representations of zero satisfy the contract of
+ * {@link Object#hashCode()}: if two objects have different hash codes, "equals" must
+ * return false. This is an issue with using {@link Double#hashCode(double)} to create
+ * hash codes and {@code ==} for equality when using different representations of
+ * zero: Double.hashCode(-0.0) != Double.hashCode(0.0) but -0.0 == 0.0 is
+ * {@code true}.
+ *
+ * @see <a
+ * href="https://issues.apache.org/jira/projects/MATH/issues/MATH-1118">MATH-1118</a>
+ */
@Test
- public void testLog10() {
- final double ln10 = Math.log(10);
- final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
- for (int i = 0; i < 10; i++) {
- final Complex z = Complex.ofCartesian(rng.nextDouble() * 2, rng.nextDouble() * 2);
- final Complex lnz = z.log();
- final Complex log10z = z.log10();
- // This is prone to floating-point error so use a delta
- Assertions.assertEquals(lnz.getReal() / ln10, log10z.getReal(), 1e-12, "real");
- // This test should be exact
- Assertions.assertEquals(lnz.getImaginary(), log10z.getImaginary(), "imag");
+ public void testHashCodeWithDifferentZeros() {
+ final double[] values = {-0.0, 0.0};
+ final ArrayList<Complex> list = createCombinations(values);
+
+ // Explicit test for issue MATH-1118
+ // "equals" and "hashCode" must be compatible
+ for (int i = 0; i < list.size(); i++) {
+ final Complex c1 = list.get(i);
+ for (int j = i + 1; j < list.size(); j++) {
+ final Complex c2 = list.get(j);
+ if (c1.hashCode() != c2.hashCode()) {
+ Assertions.assertNotEquals(c1, c2, "'equals' not compatible with 'hashCode'");
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a list of Complex numbers using an all-vs-all combination of the provided
+ * values for both the real and imaginary parts.
+ *
+ * @param values the values
+ * @return the list
+ */
+ private static ArrayList<Complex> createCombinations(final double[] values) {
+ final ArrayList<Complex> list = new ArrayList<>(values.length * values.length);
+ for (final double re : values) {
+ for (final double im : values) {
+ list.add(Complex.ofCartesian(re, im));
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Perform the smallest change to the value. This returns the next double value
+ * adjacent to d in the direction of infinity. Edge cases: if already infinity then
+ * return the next closest in the direction of negative infinity; if nan then return
+ * 0.
+ *
+ * @param x the x
+ * @return the new value
+ */
+ private static double smallestChange(double x) {
+ if (Double.isNaN(x)) {
+ return 0;
}
+ return x == Double.POSITIVE_INFINITY ? Math.nextDown(x) : Math.nextUp(x);
}
@Test
@@ -1962,11 +1979,11 @@ public class ComplexTest {
Assertions.assertEquals(1, (1 - safeLower) * (1 - safeLower));
// Can we assume 1 - y^2 = 1 when y is small
Assertions.assertEquals(1, 1 - safeLower * safeLower);
- // Can we assume Math.log1p(4 * x / y / y) = (4 * x / y / y) when big y and small x
+ // Can we assume Math.log1p(4 * x / y / y) = (4 * x / y / y) when big y and small
+ // x
final double result = 4 * safeLower / safeUpper / safeUpper;
Assertions.assertEquals(result, Math.log1p(result));
- Assertions.assertEquals(result, result - result * result / 2,
- "Expected log1p Taylor series to be redundant");
+ Assertions.assertEquals(result, result - result * result / 2, "Expected log1p Taylor series to be redundant");
// Can we assume if x != 1 then (x-1) is valid for multiplications.
Assertions.assertNotEquals(0, 1 - Math.nextUp(1));
Assertions.assertNotEquals(0, 1 - Math.nextDown(1));
@@ -2038,9 +2055,9 @@ public class ComplexTest {
}
/**
- * Test the abs and sqrt functions are consistent. The definition of sqrt uses abs
- * and the result should be computed using the same representation of the
- * complex number's magnitude (abs). If the sqrt function uses a simple representation
+ * Test the abs and sqrt functions are consistent. The definition of sqrt uses abs and
+ * the result should be computed using the same representation of the complex number's
+ * magnitude (abs). If the sqrt function uses a simple representation
* {@code sqrt(x^2 + y^2)} then this may have a 1 ulp or more difference from the high
* accuracy result computed by abs. This will propagate to create differences in sqrt.
*
@@ -2051,24 +2068,28 @@ public class ComplexTest {
public void testAbsVsSqrt() {
final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);
// Note: All methods implement scaling to ensure the magnitude can be computed.
- // Try very large or small numbers that will over/underflow to test that the scaling
+ // Try very large or small numbers that will over/underflow to test that the
+ // scaling
// is consistent. Note that:
// - sqrt will reduce the size of the real and imaginary
- // components when |z|>1 and increase them when |z|<1.
+ // components when |z|>1 and increase them when |z|<1.
- // Each sample fails approximately 3% of the time if using a standard x^2+y^2 in sqrt()
+ // Each sample fails approximately 3% of the time if using a standard x^2+y^2 in
+ // sqrt()
// and high accuracy representation in abs().
// Use 1000 samples to ensure the behavior is OK.
- // Do not use data which will over/underflow so we can use a simple computation in the test
- assertAbsVsSqrt(1000, () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1000),
- createFixedExponentNumber(rng, 1000)));
- assertAbsVsSqrt(1000, () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1000),
- createFixedExponentNumber(rng, -1000)));
+ // Do not use data which will over/underflow so we can use a simple computation in
+ // the test
+ assertAbsVsSqrt(1000,
+ () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1000), createFixedExponentNumber(rng, 1000)));
+ assertAbsVsSqrt(1000,
+ () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1000), createFixedExponentNumber(rng, -1000)));
}
private static void assertAbsVsSqrt(int samples, Supplier<Complex> supplier) {
// Note: All methods implement scaling to ensure the magnitude can be computed.
- // Try very large or small numbers that will over/underflow to test that the scaling
+ // Try very large or small numbers that will over/underflow to test that the
+ // scaling
// is consistent.
for (int i = 0; i < samples; i++) {
final Complex z = supplier.get();
@@ -2080,8 +2101,9 @@ public class ComplexTest {
// sqrt(x + iy)
// t = sqrt( 2 (|x| + |x + iy|) )
// if x >= 0: (t/2, y/t)
- // else : (|y| / t, t/2 * sgn(y))
- // Note this is not the definitional polar computation using absolute and argument:
+ // else : (|y| / t, t/2 * sgn(y))
+ // Note this is not the definitional polar computation using absolute and
+ // argument:
// real = sqrt(|z|) * cos(0.5 * arg(z))
// imag = sqrt(|z|) * sin(0.5 * arg(z))
final Complex c = z.sqrt();
@@ -2097,9 +2119,9 @@ public class ComplexTest {
}
/**
- * Test the abs and log functions are consistent. The definition of log uses abs
- * and the result should be computed using the same representation of the
- * complex number's magnitude (abs). If the log function uses a simple representation
+ * Test the abs and log functions are consistent. The definition of log uses abs and
+ * the result should be computed using the same representation of the complex number's
+ * magnitude (abs). If the log function uses a simple representation
* {@code sqrt(x^2 + y^2)} then this may have a 1 ulp or more difference from the high
* accuracy result computed by abs. This will propagate to create differences in log.
*
@@ -2110,25 +2132,30 @@ public class ComplexTest {
public void testAbsVsLog() {
final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);
// Note: All methods implement scaling to ensure the magnitude can be computed.
- // Try very large or small numbers that will over/underflow to test that the scaling
+ // Try very large or small numbers that will over/underflow to test that the
+ // scaling
// is consistent. Note that:
// - log will set the real component using log(|z|). This will massively reduce
- // the magnitude when |z| >> 1. Highest accuracy will be when |z| is as large
- // as possible before computing the log.
+ // the magnitude when |z| >> 1. Highest accuracy will be when |z| is as large
+ // as possible before computing the log.
- // No test around |z| == 1 as a high accuracy computation is required: Math.log1p(x*x+y*y-1)
+ // No test around |z| == 1 as a high accuracy computation is required:
+ // Math.log1p(x*x+y*y-1)
- // Each sample fails approximately 25% of the time if using a standard x^2+y^2 in log()
- // and high accuracy representation in abs(). Use 100 samples to ensure the behavior is OK.
- assertAbsVsLog(100, () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1022),
- createFixedExponentNumber(rng, 1022)));
- assertAbsVsLog(100, () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1022),
- createFixedExponentNumber(rng, -1022)));
+ // Each sample fails approximately 25% of the time if using a standard x^2+y^2 in
+ // log()
+ // and high accuracy representation in abs(). Use 100 samples to ensure the
+ // behavior is OK.
+ assertAbsVsLog(100,
+ () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1022), createFixedExponentNumber(rng, 1022)));
+ assertAbsVsLog(100,
+ () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1022), createFixedExponentNumber(rng, -1022)));
}
private static void assertAbsVsLog(int samples, Supplier<Complex> supplier) {
// Note: All methods implement scaling to ensure the magnitude can be computed.
- // Try very large or small numbers that will over/underflow to test that the scaling
+ // Try very large or small numbers that will over/underflow to test that the
+ // scaling
// is consistent.
for (int i = 0; i < samples; i++) {
final Complex z = supplier.get();