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 2019/12/13 01:20:44 UTC

[commons-numbers] branch master updated: [NUMBERS-139] Implement multiply and divide for imaginary only.

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


The following commit(s) were added to refs/heads/master by this push:
     new 007cc20  [NUMBERS-139] Implement multiply and divide for imaginary only.
007cc20 is described below

commit 007cc20e9f84e41a65359b922d01b0247f975157
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Thu Dec 12 21:53:23 2019 +0000

    [NUMBERS-139] Implement multiply and divide for imaginary only.
    
    multiplyImaginary(double)
    divideImaginary(double)
    
    Document that the method may return different signs than the
    corresponding Complex method for the result parts if the complex has
    zeros of the argument of the function is zero.
    
    The documentation of the differences requires full unit test of all
    cases. The exact cases that differ may be individually documented.
---
 .../apache/commons/numbers/complex/Complex.java    | 109 +++++-
 .../commons/numbers/complex/ComplexTest.java       | 429 ++++++++++++++++++---
 2 files changed, 459 insertions(+), 79 deletions(-)

diff --git a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
index b65e62f..0d05f85 100644
--- a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
+++ b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java
@@ -469,14 +469,13 @@ public final class Complex implements Serializable  {
     }
 
     /**
-     * Returns a {@code Complex} whose value is
-     * {@code (this / divisor)}.
+     * Returns a {@code Complex} whose value is {@code (this / divisor)}.
      * Implements the formula:
      * <pre>
      * <code>
-     *   a + b i     ac + bd + i (bc - ad)
-     *   -------  =  ---------------------
-     *   c + d i           c<sup>2</sup> + d<sup>2</sup>
+     *   a + i b     (ac + bd) + i (bc - ad)
+     *   -------  =  -----------------------
+     *   c + i d            c<sup>2</sup> + d<sup>2</sup>
      * </code>
      * </pre>
      *
@@ -496,9 +495,9 @@ public final class Complex implements Serializable  {
      * Returns a {@code Complex} whose value is:
      * <pre>
      * <code>
-     *   a + b i     ac + bd + i (bc - ad)
-     *   -------  =  ---------------------
-     *   c + d i           c<sup>2</sup> + d<sup>2</sup>
+     *   a + i b     (ac + bd) + i (bc - ad)
+     *   -------  =  -----------------------
+     *   c + i d            c<sup>2</sup> + d<sup>2</sup>
      * </code>
      * </pre>
      *
@@ -510,7 +509,7 @@ public final class Complex implements Serializable  {
      * @param im1 Imaginary component of first number.
      * @param re2 Real component of second number.
      * @param im2 Imaginary component of second number.
-     * @return (a + b i) / (c + d i).
+     * @return (a + i b) / (c + i d).
      * @see <a href="http://mathworld.wolfram.com/ComplexDivision.html">Complex Division</a>
      */
     private static Complex divide(double re1, double im1, double re2, double im2) {
@@ -610,15 +609,51 @@ public final class Complex implements Serializable  {
      * with {@code divisor} interpreted as a real number.
      * Implements the formula:
      * <pre>
-     *   (a + b i) / c = (a + b i) / (c + 0 i)
+     *   (a + i b) / c = (a + i b) / (c + i 0)
+     *                 = (a/c) + i (b/c)
      * </pre>
      *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * real-only and complex numbers.</p>
+     *
+     * <p>Note: Due to floating-point addition arithmetic on negative zeros this method should be
+     * preferred over using {@link #divide(Complex) divide(Complex.ofCartesian(factor, 0))}. If
+     * {@code this} complex has zeros for the real and/or imaginary component, or the factor
+     * is zero, the sign of the zero or infinite components of the result may differ between the
+     * two divide methods.
+     *
      * @param  divisor Value by which this {@code Complex} is to be divided.
      * @return {@code this / divisor}.
      * @see #divide(Complex)
      */
     public Complex divide(double divisor) {
-        return divide(new Complex(divisor, 0));
+        return new Complex(real / divisor, imaginary / divisor);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this / divisor)},
+     * with {@code divisor} interpreted as an imaginary number.
+     * Implements the formula:
+     * <pre>
+     *   (a + i b) / id = (a + i b) / (0 + i d)
+     *                  = (b/d) + i (-a/d)
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * imaginary-only and complex numbers.</p>
+     *
+     * <p>Note: Due to floating-point addition arithmetic on negative zeros this method should be
+     * preferred over using {@link #divide(Complex) divide(Complex.ofCartesian(0, factor))}. If
+     * {@code this} complex has zeros for the real and/or imaginary component, or the factor
+     * is zero, the sign of the zero or infinite components of the result may differ between the
+     * two divide methods.
+     *
+     * @param  divisor Value by which this {@code Complex} is to be divided.
+     * @return {@code this / divisor}.
+     * @see #divide(Complex)
+     */
+    public Complex divideImaginary(double divisor) {
+        return new Complex(imaginary / divisor, -real / divisor);
     }
 
     /**
@@ -851,7 +886,7 @@ public final class Complex implements Serializable  {
      * Returns a {@code Complex} whose value is {@code this * factor}.
      * Implements the formula:
      * <pre>
-     *   (a + b i)(c + d i) = (ac - bd) + i (ad + bc)
+     *   (a + i b)(c + i d) = (ac - bd) + i (ad + bc)
      * </pre>
      *
      * <p>Recalculates to recover infinities as specified in C.99
@@ -869,7 +904,7 @@ public final class Complex implements Serializable  {
     /**
      * Returns a {@code Complex} whose value is:
      * <pre>
-     *   (a + b i)(c + d i) = (ac - bd) + i (ad + bc)
+     *   (a + i b)(c + i d) = (ac - bd) + i (ad + bc)
      * </pre>
      *
      * <p>Recalculates to recover infinities as specified in C.99
@@ -997,10 +1032,19 @@ public final class Complex implements Serializable  {
      * interpreted as a real number.
      * Implements the formula:
      * <pre>
-     *   (a + b i) c = (a + b i)(c + 0 i)
-     *               = ac + bc i
+     *   (a + i b) c = (a + i b)(c + 0 i)
+     *               = (ac) + i (bc)
      * </pre>
      *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * real-only and complex numbers.</p>
+     *
+     * <p>Note: Due to floating-point addition arithmetic on negative zeros this method should be
+     * preferred over using {@link #multiply(Complex) multiply(Complex.ofCartesian(factor, 0))}. If
+     * {@code this} complex has zeros for the real and/or imaginary component, or the factor
+     * is zero, the sign of the zero components of the result may differ between the two multiply
+     * methods.
+     *
      * @param  factor value to be multiplied by this {@code Complex}.
      * @return {@code this * factor}.
      * @see #multiply(Complex)
@@ -1010,6 +1054,41 @@ public final class Complex implements Serializable  {
     }
 
     /**
+     * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor}
+     * interpreted as an imaginary number.
+     * Implements the formula:
+     * <pre>
+     *   (a + i b) id = (a + i b)(0 + i d)
+     *                = (-bd) + i (ad)
+     * </pre>
+     *
+     * <p>This method can be used to compute the multiplication of this complex number {@code z}
+     * by {@code i}. This should be used in preference to
+     * {@link #multiply(Complex) multiply(Complex.I)} with or without {@link #negate() negation}:</p>
+     *
+     * <pre>
+     *   iz = (-b + i a) = this.multiply(1);
+     *  -iz = (b + i -a) = this.multiply(-1);
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * imaginary-only and complex numbers.</p>
+     *
+     * <p>Note: Due to floating-point addition arithmetic on negative zeros this method should be
+     * preferred over using {@link #multiply(Complex) multiply(Complex.ofCartesian(0, factor))}. If
+     * {@code this} complex has zeros for the real and/or imaginary component, or the factor
+     * is zero, the sign of the zero components of the result may differ between the two multiply
+     * methods.
+     *
+     * @param  factor value to be multiplied by this {@code Complex}.
+     * @return {@code this * factor}.
+     * @see #multiply(Complex)
+     */
+    public Complex multiplyImaginary(double factor) {
+        return new Complex(-imaginary * factor, real * factor);
+    }
+
+    /**
      * Returns a {@code Complex} whose value is {@code (-this)}.
      *
      * @return {@code -this}.
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 13c0853..c9c0ee1 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
@@ -68,21 +68,21 @@ public class ComplexTest {
     }
 
     /**
-    * Create a complex number given the real part.
-    *
-    * @param real Real part.
-    * @return {@code Complex} object
-    */
+     * Create a complex number given the real part.
+     *
+     * @param real Real part.
+     * @return {@code Complex} object
+     */
     private static Complex ofReal(double real) {
         return Complex.ofCartesian(real, 0);
     }
 
     /**
-    * Create a complex number given the imaginary part.
-    *
-    * @param imaginary Imaginary part.
-    * @return {@code Complex} object
-    */
+     * Create a complex number given the imaginary part.
+     *
+     * @param imaginary Imaginary part.
+     * @return {@code Complex} object
+     */
     private static Complex ofImaginary(double imaginary) {
         return Complex.ofCartesian(0, imaginary);
     }
@@ -270,7 +270,9 @@ public class ComplexTest {
         Assertions.assertEquals(8.0, z.getReal());
         Assertions.assertEquals(-0.0, z.getImaginary(), "Expected sign preservation");
         // Sign-preservation is a problem: -0.0 + 0.0 == 0.0
-        Assertions.assertNotEquals(z, x.add(ofReal(y)));
+        Complex z2 = x.add(ofReal(y));
+        Assertions.assertEquals(8.0, z2.getReal());
+        Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no-sign preservation");
     }
 
     @Test
@@ -311,10 +313,12 @@ public class ComplexTest {
         final Complex x = Complex.ofCartesian(-0.0, 4.0);
         final double y = 5.0;
         Complex z = x.addImaginary(y);
-        Assertions.assertEquals(-0.0, z.getReal());
-        Assertions.assertEquals(9.0, z.getImaginary(), "Expected sign preservation");
+        Assertions.assertEquals(-0.0, z.getReal(), "Expected sign preservation");
+        Assertions.assertEquals(9.0, z.getImaginary());
         // Sign-preservation is a problem: -0.0 + 0.0 == 0.0
-        Assertions.assertNotEquals(z, x.add(ofImaginary(y)));
+        Complex z2 = x.add(ofImaginary(y));
+        Assertions.assertEquals(0.0, z2.getReal(), "Expected no-sign preservation");
+        Assertions.assertEquals(9.0, z2.getImaginary());
     }
 
     @Test
@@ -332,11 +336,11 @@ public class ComplexTest {
     }
 
     @Test
-    public void testConjugateInfiinite() {
+    public void testConjugateInfinite() {
         Complex z = Complex.ofCartesian(0, inf);
-        Assertions.assertEquals(neginf, z.conj().getImaginary(), 0);
+        Assertions.assertEquals(neginf, z.conj().getImaginary());
         z = Complex.ofCartesian(0, neginf);
-        Assertions.assertEquals(inf, z.conj().getImaginary(), 0);
+        Assertions.assertEquals(inf, z.conj().getImaginary());
     }
 
     @Test
@@ -349,21 +353,6 @@ public class ComplexTest {
     }
 
     @Test
-    public void testDivideReal() {
-        final Complex x = Complex.ofCartesian(2d, 3d);
-        final Complex y = Complex.ofCartesian(2d, 0d);
-        Assertions.assertEquals(Complex.ofCartesian(1.0, 1.5), x.divide(y));
-
-    }
-
-    @Test
-    public void testDivideImaginary() {
-        final Complex x = Complex.ofCartesian(2d, 3d);
-        final Complex y = Complex.ofCartesian(0d, 2d);
-        Assertions.assertEquals(Complex.ofCartesian(1.5d, -1.0), x.divide(y));
-    }
-
-    @Test
     public void testDivideZero() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final Complex z = x.divide(Complex.ZERO);
@@ -400,25 +389,131 @@ public class ComplexTest {
     }
 
     @Test
-    public void testScalarDivide() {
+    public void testDivideReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = 2.0;
-        final Complex yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.divide(yComplex), x.divide(yDouble));
+        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 testScalarDivideNaN() {
+    public void testDivideRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = Double.NaN;
-        final Complex yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.divide(yComplex), x.divide(yDouble));
+        final double y = nan;
+        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;
+        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 testScalarDivideZero() {
-        final Complex x = Complex.ofCartesian(1, 1);
-        TestUtils.assertEquals(x.divide(Complex.ZERO), x.divide(0), 0);
+    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());
     }
 
     @Test
@@ -478,41 +573,247 @@ public class ComplexTest {
 
     @Test
     public void testMultiplyInfInf() {
-        // Assert.assertTrue(infInf.multiply(infInf).isNaN()); // MATH-620
-        Assertions.assertTrue(infInf.multiply(infInf).isInfinite());
+        final Complex z = infInf.multiply(infInf);
+        // Assert.assertTrue(z.isNaN()); // MATH-620
+        Assertions.assertTrue(z.isInfinite());
+
+        // Expected results from g++:
+        Assertions.assertEquals(Complex.ofCartesian(nan, inf), infInf.multiply(infInf));
+        Assertions.assertEquals(Complex.ofCartesian(inf, nan), infInf.multiply(infNegInf));
+        Assertions.assertEquals(Complex.ofCartesian(-inf, nan), infInf.multiply(negInfInf));
+        Assertions.assertEquals(Complex.ofCartesian(nan, -inf), infInf.multiply(negInfNegInf));
     }
 
     @Test
-    public void testScalarMultiply() {
+    public void testMultiplyReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = 2.0;
-        final Complex yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.multiply(yComplex), x.multiply(yDouble));
-        final int zInt = -5;
-        final Complex zComplex = ofReal(zInt);
-        Assertions.assertEquals(x.multiply(zComplex), x.multiply(zInt));
+        final double y = 2.0;
+        Complex z = x.multiply(y);
+        Assertions.assertEquals(6.0, z.getReal());
+        Assertions.assertEquals(8.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(y)));
+
+        z = x.multiply(-y);
+        Assertions.assertEquals(-6.0, z.getReal());
+        Assertions.assertEquals(-8.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(-y)));
     }
 
     @Test
-    public void testScalarMultiplyNaN() {
+    public void testMultiplyRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = Double.NaN;
-        final Complex yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.multiply(yComplex), x.multiply(yDouble));
+        final double y = nan;
+        Complex z = x.multiply(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(nan, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(y)));
+    }
+
+    @Test
+    public void testMultiplyRealInf() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = inf;
+        Complex z = x.multiply(y);
+        Assertions.assertEquals(inf, z.getReal());
+        Assertions.assertEquals(inf, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(y)));
+
+        z = x.multiply(-y);
+        Assertions.assertEquals(-inf, z.getReal());
+        Assertions.assertEquals(-inf, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(-y)));
+    }
+
+    @Test
+    public void testMultiplyRealZero() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 0.0;
+        Complex z = x.multiply(y);
+        Assertions.assertEquals(0.0, z.getReal());
+        Assertions.assertEquals(0.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofReal(y)));
+
+        z = x.multiply(-y);
+        Assertions.assertEquals(-0.0, z.getReal());
+        Assertions.assertEquals(-0.0, z.getImaginary());
+        // Sign-preservation is a problem for imaginary: 0.0 - -0.0 == 0.0
+        Complex z2 = x.multiply(ofReal(-y));
+        Assertions.assertEquals(-0.0, z2.getReal());
+        Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no sign preservation");
+    }
+
+    @Test
+    public void testMultiplyImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 2.0;
+        Complex z = x.multiplyImaginary(y);
+        Assertions.assertEquals(-8.0, z.getReal());
+        Assertions.assertEquals(6.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
+
+        z = x.multiplyImaginary(-y);
+        Assertions.assertEquals(8.0, z.getReal());
+        Assertions.assertEquals(-6.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofImaginary(-y)));
+    }
+
+    @Test
+    public void testMultiplyImaginaryNaN() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = nan;
+        Complex z = x.multiplyImaginary(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(nan, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
+    }
+
+    @Test
+    public void testMultiplyImaginaryInf() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = inf;
+        Complex z = x.multiplyImaginary(y);
+        Assertions.assertEquals(-inf, z.getReal());
+        Assertions.assertEquals(inf, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
+
+        z = x.multiplyImaginary(-y);
+        Assertions.assertEquals(inf, z.getReal());
+        Assertions.assertEquals(-inf, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.multiply(ofImaginary(-y)));
+    }
+
+    @Test
+    public void testMultiplyImaginaryZero() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 0.0;
+        Complex z = x.multiplyImaginary(y);
+        Assertions.assertEquals(-0.0, z.getReal());
+        Assertions.assertEquals(0.0, z.getImaginary());
+        // Sign-preservation is a problem for real: 0.0 - -0.0 == 0.0
+        Complex z2 = x.multiply(ofImaginary(y));
+        Assertions.assertEquals(0.0, z2.getReal(), "Expected no sign preservation");
+        Assertions.assertEquals(0.0, z2.getImaginary());
+
+        z = x.multiplyImaginary(-y);
+        Assertions.assertEquals(0.0, z.getReal());
+        Assertions.assertEquals(-0.0, z.getImaginary());
+        // Sign-preservation is a problem for imaginary: -0.0 - 0.0 == 0.0
+        z2 = x.multiply(ofImaginary(-y));
+        Assertions.assertEquals(0.0, z2.getReal());
+        Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no sign preservation");
+    }
+
+    @Test
+    public void testNonZeroMultiplyI() {
+        final double[] parts = {3.0, 4.0};
+        for (final double a : parts) {
+            for (final double b : parts) {
+                final Complex c = Complex.ofCartesian(a, b);
+                final Complex x = c.multiplyImaginary(1.0);
+                // Check verses algebra solution
+                Assertions.assertEquals(-b, x.getReal());
+                Assertions.assertEquals(a, x.getImaginary());
+                final Complex z = c.multiply(Complex.I);
+                Assertions.assertEquals(x, z);
+            }
+        }
     }
 
     @Test
-    public void testScalarMultiplyInf() {
-        final Complex x = Complex.ofCartesian(1, 1);
-        double yDouble = Double.POSITIVE_INFINITY;
-        Complex yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.multiply(yComplex), x.multiply(yDouble));
+    public void testNonZeroMultiplyNegativeI() {
+        // This works no matter how you represent -I as a Complex
+        final double[] parts = {3.0, 4.0};
+        final Complex[] negIs = {Complex.ofCartesian(-0.0, -1), Complex.ofCartesian(0.0, -1)};
+        for (final double a : parts) {
+            for (final double b : parts) {
+                final Complex c = Complex.ofCartesian(a, b);
+                final Complex x = c.multiplyImaginary(-1.0);
+                // Check verses algebra solution
+                Assertions.assertEquals(b, x.getReal());
+                Assertions.assertEquals(-a, x.getImaginary());
+                for (Complex negI : negIs) {
+                    final Complex z = c.multiply(negI);
+                    Assertions.assertEquals(x, z);
+                }
+            }
+        }
+    }
 
-        yDouble = Double.NEGATIVE_INFINITY;
-        yComplex = ofReal(yDouble);
-        Assertions.assertEquals(x.multiply(yComplex), x.multiply(yDouble));
+    @Test
+    public void testZeroMultiplyI() {
+        final double[] zeros = {-0.0, 0.0};
+        for (double a : zeros) {
+            for (double b : zeros) {
+                final Complex c = Complex.ofCartesian(a, b);
+                Complex x = c.multiplyImaginary(1.0);
+                // Check verses algebra solution
+                Assertions.assertEquals(-b, x.getReal());
+                Assertions.assertEquals(a, x.getImaginary());
+                Complex z = c.multiply(Complex.I);
+                // Does not work when imaginary part is +0.0.
+                if (Double.compare(b, 0.0) == 0) {
+                    // (-0.0, 0.0).multiply( (0,1) ) => (-0.0, 0.0)   expected (-0.0,-0.0)
+                    // ( 0.0, 0.0).multiply( (0,1) ) => ( 0.0, 0.0)   expected (-0.0, 0.0)
+                    Assertions.assertEquals(0, z.getReal(), 0.0);
+                    Assertions.assertEquals(0, z.getImaginary(), 0.0);
+                    Assertions.assertNotEquals(x, z);
+                } else {
+                    Assertions.assertEquals(x, z);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testZeroMultiplyNegativeI() {
+        // 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.
+        Complex negI = Complex.I.negate();
+        final double[] zeros = {-0.0, 0.0};
+        for (double a : zeros) {
+            for (double b : zeros) {
+                final Complex c = Complex.ofCartesian(a, b);
+                Complex x = c.multiplyImaginary(-1.0);
+                // Check verses algebra solution
+                Assertions.assertEquals(b, x.getReal());
+                Assertions.assertEquals(-a, x.getImaginary());
+                Complex z = c.multiply(negI);
+                Complex z2 = c.multiply(Complex.I).negate();
+                // Does not work when imaginary part is -0.0.
+                if (Double.compare(b, -0.0) == 0) {
+                    // (-0.0,-0.0).multiply( (-0.0,-1) ) => ( 0.0, 0.0)   expected (-0.0, 0.0)
+                    // ( 0.0,-0.0).multiply( (-0.0,-1) ) => (-0.0, 0.0)   expected (-0.0,-0.0)
+                    Assertions.assertEquals(0, z.getReal(), 0.0);
+                    Assertions.assertEquals(0, z.getImaginary(), 0.0);
+                    Assertions.assertNotEquals(x, z);
+                    // When multiply by I.negate() fails multiply by I then negate() works!
+                    Assertions.assertEquals(x, z2);
+                } else {
+                    Assertions.assertEquals(x, z);
+                    // When multiply by I.negate() works multiply by I then negate() fails!
+                    Assertions.assertNotEquals(x, z2);
+                }
+            }
+        }
     }
 
+    // TODO
+    // Add a comprehensive arithmetic test using combinations of +/-0.0 for real, imaginary and
+    // and the double argument for add, subtract, subtractFrom, multiply, divide.
+    // The test should count the number of differences between the complex version and
+    // the non-complex version. Any with differences above zero should be documented in Complex.
+
     @Test
     public void testNegate() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);