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/12 00:22:11 UTC

[commons-numbers] branch master updated: [NUMBERS-139] Implement add and subtract 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 d96bc97  [NUMBERS-139] Implement add and subtract for imaginary only.
d96bc97 is described below

commit d96bc97cfdca5b36d3f8b2561f60c7cec72761fe
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Thu Dec 12 00:22:07 2019 +0000

    [NUMBERS-139] Implement add and subtract for imaginary only.
    
    addImaginary(double)
    subtractImaginary(double)
    
    subtractFrom(double)
    subtractFromImaginary(double)
---
 .../apache/commons/numbers/complex/Complex.java    | 126 ++++++++-
 .../commons/numbers/complex/ComplexTest.java       | 303 ++++++++++++++++++---
 2 files changed, 379 insertions(+), 50 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 0181189..f3ac498 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
@@ -332,11 +332,10 @@ public final class Complex implements Serializable  {
     }
 
     /**
-     * Returns a {@code Complex} whose value is
-     * {@code (this + addend)}.
+     * Returns a {@code Complex} whose value is {@code (this + addend)}.
      * Implements the formula:
      * <pre>
-     *   (a + b i) + (c + d i) = (a + c) + i (b + d)
+     *   (a + i b) + (c + i d) = (a + c) + i (b + d)
      * </pre>
      *
      * @param  addend Value to be added to this {@code Complex}.
@@ -351,16 +350,54 @@ public final class Complex implements Serializable  {
     /**
      * Returns a {@code Complex} whose value is {@code (this + addend)},
      * with {@code addend} interpreted as a real number.
+     * Implements the formula:
+     * <pre>
+     *  (a + i b) + c = (a + c) + i b
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * real-only and complex numbers.</p>
+     *
+     * <p>Note: This method preserves the sign of the imaginary component {@code b} if it is {@code -0.0}.
+     * The sign would be lost if adding {@code (c + i 0)} using
+     * {@link #add(Complex) add(Complex.ofReal(addend))} since
+     * {@code -0.0 + 0.0 = 0.0}.
      *
      * @param addend Value to be added to this {@code Complex}.
      * @return {@code this + addend}.
      * @see #add(Complex)
+     * @see #ofReal(double)
      */
     public Complex add(double addend) {
         return new Complex(real + addend, imaginary);
     }
 
     /**
+     * Returns a {@code Complex} whose value is {@code (this + addend)},
+     * with {@code addend} interpreted as an imaginary number.
+     * Implements the formula:
+     * <pre>
+     *  (a + i b) + i d = a + i (b + d)
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * imaginary-only and complex numbers.</p>
+     *
+     * <p>Note: This method preserves the sign of the real component {@code a} if it is {@code -0.0}.
+     * The sign would be lost if adding {@code (0 + i d)} using
+     * {@link #add(Complex) add(Complex.ofCartesian(0, addend))} since
+     * {@code -0.0 + 0.0 = 0.0}.
+     *
+     * @param addend Value to be added to this {@code Complex}.
+     * @return {@code this + addend}.
+     * @see #add(Complex)
+     * @see #ofCartesian(double, double)
+     */
+    public Complex addImaginary(double addend) {
+        return new Complex(real, imaginary + addend);
+    }
+
+    /**
      * Returns the
      * <a href="http://mathworld.wolfram.com/ComplexConjugate.html">conjugate</a>
      * z&#773; of this complex number z.
@@ -926,11 +963,10 @@ public final class Complex implements Serializable  {
     }
 
     /**
-     * Returns a {@code Complex} whose value is
-     * {@code (this - subtrahend)}.
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)}.
      * Implements the formula:
      * <pre>
-     *  (a + b i) - (c + d i) = (a - c) + i (b - d)
+     *  (a + i b) - (c + i d) = (a - c) + i (b - d)
      * </pre>
      *
      * @param  subtrahend value to be subtracted from this {@code Complex}.
@@ -943,13 +979,16 @@ public final class Complex implements Serializable  {
     }
 
     /**
-     * Returns a {@code Complex} whose value is
-     * {@code (this - subtrahend)}.
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)},
+     * with {@code subtrahend} interpreted as a real number.
      * Implements the formula:
      * <pre>
-     *  (a + b i) - c = (a - c) + b i
+     *  (a + i b) - c = (a - c) + i b
      * </pre>
      *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * real-only and complex numbers.</p>
+     *
      * @param  subtrahend value to be subtracted from this {@code Complex}.
      * @return {@code this - subtrahend}.
      * @see #subtract(Complex)
@@ -959,6 +998,75 @@ public final class Complex implements Serializable  {
     }
 
     /**
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)},
+     * with {@code subtrahend} interpreted as an imaginary number.
+     * Implements the formula:
+     * <pre>
+     *  (a + i b) - i d = a + i (b - d)
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * imaginary-only and complex numbers.</p>
+     *
+     * @param  subtrahend value to be subtracted from this {@code Complex}.
+     * @return {@code this - subtrahend}.
+     * @see #subtract(Complex)
+     */
+    public Complex subtractImaginary(double subtrahend) {
+        return new Complex(real, imaginary - subtrahend);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (minuend - this)},
+     * with {@code minuend} interpreted as a real number.
+     * Implements the formula:
+     * <pre>
+     *  c - (a + i b) = (c - a) - i b
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * real-only and complex numbers.</p>
+     *
+     * <p>Note: This method inverts the sign of the imaginary component {@code b} if it is {@code 0.0}.
+     * The sign would not be inverted if subtracting from {@code (c + i 0)} using
+     * {@link #subtract(Complex) Complex.ofReal(minuend).subtract(this))} since
+     * {@code 0.0 - 0.0 = 0.0}.
+     *
+     * @param  minuend value this {@code Complex} is to be subtracted from.
+     * @return {@code minuend - this}.
+     * @see #subtract(Complex)
+     * @see #ofReal(double)
+     */
+    public Complex subtractFrom(double minuend) {
+        return new Complex(minuend - real, -imaginary);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)},
+     * with {@code minuend} interpreted as an imaginary number.
+     * Implements the formula:
+     * <pre>
+     *  i d - (a + i b) = -a + i (d - b)
+     * </pre>
+     *
+     * <p>This method is included for compatibility with ISO C99 which defines arithmetic between
+     * imaginary-only and complex numbers.</p>
+     *
+     * <p>Note: This method inverts the sign of the real component {@code a} if it is {@code 0.0}.
+     * The sign would not be inverted if subtracting from {@code (0 + i d)} using
+     * {@link #subtract(Complex) Complex.ofCartesian(0, minuend).subtract(this))} since
+     * {@code 0.0 - 0.0 = 0.0}.
+     *
+     * @param  minuend value this {@code Complex} is to be subtracted from.
+     * @return {@code this - subtrahend}.
+     * @see #subtract(Complex)
+     * @see #ofCartesian(double, double)
+     */
+    public Complex subtractFromImaginary(double minuend) {
+        return new Complex(-real, minuend - imaginary);
+    }
+
+    /**
      * Compute the
      * <a href="http://mathworld.wolfram.com/InverseCosine.html">
      * inverse cosine</a> of this complex number.
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 4647052..217588b 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
@@ -67,6 +67,16 @@ public class ComplexTest {
         FINITE
     }
 
+    /**
+     * Convenience constructor for testing using only and imaginary component.
+     *
+     * @param imaginary the imaginary part
+     * @return the complex
+     */
+    private static Complex ofImaginary(double imaginary) {
+        return Complex.ofCartesian(0, imaginary);
+    }
+
     @Test
     public void testCartesianConstructor() {
         final Complex z = Complex.ofCartesian(3.0, 4.0);
@@ -209,40 +219,99 @@ public class ComplexTest {
         Complex x = Complex.ofCartesian(1, 1);
         final Complex z = Complex.ofCartesian(inf, 0);
         final Complex w = x.add(z);
-        Assertions.assertEquals(1, w.getImaginary(), 0);
-        Assertions.assertEquals(inf, w.getReal(), 0);
+        Assertions.assertEquals(1, w.getImaginary());
+        Assertions.assertEquals(inf, w.getReal());
 
         x = Complex.ofCartesian(neginf, 0);
         Assertions.assertTrue(Double.isNaN(x.add(z).getReal()));
     }
 
+    @Test
+    public void testAddReal() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 5.0;
+        Complex z = x.add(y);
+        Assertions.assertEquals(8.0, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(Complex.ofReal(y)));
+    }
 
     @Test
-    public void testScalarAdd() {
+    public void testAddRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = 2.0;
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.add(yComplex), x.add(yDouble));
+        final double y = nan;
+        Complex z = x.add(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(Complex.ofReal(y)));
     }
 
     @Test
-    public void testScalarAddNaN() {
+    public void testAddRealInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = Double.NaN;
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.add(yComplex), x.add(yDouble));
+        final double y = inf;
+        Complex z = x.add(y);
+        Assertions.assertEquals(inf, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(Complex.ofReal(y)));
     }
 
     @Test
-    public void testScalarAddInf() {
-        Complex x = Complex.ofCartesian(1, 1);
-        final double yDouble = Double.POSITIVE_INFINITY;
+    public void testAddRealWithNegZeroImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, -0.0);
+        final double y = 5.0;
+        Complex z = x.add(y);
+        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(Complex.ofReal(y)));
+    }
 
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.add(yComplex), x.add(yDouble));
+    @Test
+    public void testAddImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 5.0;
+        Complex z = x.addImaginary(y);
+        Assertions.assertEquals(3.0, z.getReal());
+        Assertions.assertEquals(9.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(ofImaginary(y)));
+    }
 
-        x = Complex.ofCartesian(neginf, 0);
-        Assertions.assertEquals(x.add(yComplex), x.add(yDouble));
+    @Test
+    public void testAddImaginaryNaN() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = nan;
+        Complex z = x.addImaginary(y);
+        Assertions.assertEquals(3.0, z.getReal());
+        Assertions.assertEquals(nan, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(ofImaginary(y)));
+    }
+
+    @Test
+    public void testAddImaginaryInf() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = inf;
+        Complex z = x.addImaginary(y);
+        Assertions.assertEquals(3.0, z.getReal());
+        Assertions.assertEquals(inf, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.add(ofImaginary(y)));
+    }
+
+    @Test
+    public void testAddImaginaryWithNegZeroReal() {
+        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");
+        // Sign-preservation is a problem: -0.0 + 0.0 == 0.0
+        Assertions.assertNotEquals(z, x.add(ofImaginary(y)));
     }
 
     @Test
@@ -458,49 +527,201 @@ public class ComplexTest {
     @Test
     public void testSubtract() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final Complex y = Complex.ofCartesian(5.0, 6.0);
+        final Complex y = Complex.ofCartesian(5.0, 7.0);
         final Complex z = x.subtract(y);
         Assertions.assertEquals(-2.0, z.getReal());
-        Assertions.assertEquals(-2.0, z.getImaginary());
+        Assertions.assertEquals(-3.0, z.getImaginary());
     }
 
     @Test
     public void testSubtractInf() {
-        Complex x = Complex.ofCartesian(1, 1);
-        final Complex z = Complex.ofCartesian(neginf, 0);
-        final Complex w = x.subtract(z);
-        Assertions.assertEquals(1, w.getImaginary(), 0);
-        Assertions.assertEquals(inf, w.getReal(), 0);
+        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());
 
-        x = Complex.ofCartesian(neginf, 0);
-        Assertions.assertTrue(Double.isNaN(x.subtract(z).getReal()));
+        z = y.subtract(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(0.0, z.getImaginary());
     }
 
     @Test
-    public void testScalarSubtract() {
+    public void testSubtractReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = 2.0;
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.subtract(yComplex), x.subtract(yDouble));
+        final double y = 5.0;
+        Complex z = x.subtract(y);
+        Assertions.assertEquals(-2.0, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.subtract(Complex.ofReal(y)));
     }
 
     @Test
-    public void testScalarSubtractNaN() {
+    public void testSubtractRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
-        final double yDouble = Double.NaN;
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.subtract(yComplex), x.subtract(yDouble));
+        final double y = nan;
+        Complex z = x.subtract(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.subtract(Complex.ofReal(y)));
     }
 
     @Test
-    public void testScalarSubtractInf() {
-        Complex x = Complex.ofCartesian(1, 1);
-        final double yDouble = Double.POSITIVE_INFINITY;
-        final Complex yComplex = Complex.ofReal(yDouble);
-        Assertions.assertEquals(x.subtract(yComplex), x.subtract(yDouble));
+    public void testSubtractRealInf() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = inf;
+        Complex z = x.subtract(y);
+        Assertions.assertEquals(-inf, z.getReal());
+        Assertions.assertEquals(4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, x.subtract(Complex.ofReal(y)));
+    }
 
-        x = Complex.ofCartesian(neginf, 0);
-        Assertions.assertEquals(x.subtract(yComplex), x.subtract(yDouble));
+    @Test
+    public void testSubtractRealWithNegZeroImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, -0.0);
+        final double y = 5.0;
+        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(Complex.ofReal(y)));
+    }
+
+    @Test
+    public void testSubtractImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 5.0;
+        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;
+        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;
+        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;
+        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;
+        Complex z = x.subtractFrom(y);
+        Assertions.assertEquals(2.0, z.getReal());
+        Assertions.assertEquals(-4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, Complex.ofReal(y).subtract(x));
+    }
+
+    @Test
+    public void testSubtractFromRealNaN() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = nan;
+        Complex z = x.subtractFrom(y);
+        Assertions.assertEquals(nan, z.getReal());
+        Assertions.assertEquals(-4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, Complex.ofReal(y).subtract(x));
+    }
+
+    @Test
+    public void testSubtractFromRealInf() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = inf;
+        Complex z = x.subtractFrom(y);
+        Assertions.assertEquals(inf, z.getReal());
+        Assertions.assertEquals(-4.0, z.getImaginary());
+        // Equivalent
+        Assertions.assertEquals(z, Complex.ofReal(y).subtract(x));
+    }
+
+    @Test
+    public void testSubtractFromRealWithPosZeroImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, 0.0);
+        final double y = 5.0;
+        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, Complex.ofReal(y).subtract(x));
+    }
+
+    @Test
+    public void testSubtractFromImaginary() {
+        final Complex x = Complex.ofCartesian(3.0, 4.0);
+        final double y = 5.0;
+        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;
+        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;
+        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;
+        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