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();