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/16 22:42:58 UTC

[commons-numbers] branch master updated (0d66b94 -> 15c7706)

This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git.


    from 0d66b94  NUMBERS-140: Multidimensional counter.
     new 9a2c290  Use charAt(int) to test for the start and end characters.
     new a4b33d4  Improve documentation of sign differences in +/-0.0 for arithmetic.
     new 15c7706  Use final.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/commons/numbers/complex/Complex.java    |  95 +++++---
 .../commons/numbers/complex/ComplexTest.java       | 257 ++++++++++++++++-----
 2 files changed, 260 insertions(+), 92 deletions(-)


[commons-numbers] 01/03: Use charAt(int) to test for the start and end characters.

Posted by ah...@apache.org.
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 9a2c29077c15e8dff0b5d285d0b3191c62e25d40
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Sat Dec 14 16:14:58 2019 +0000

    Use charAt(int) to test for the start and end characters.
    
    Change parse exception so that the error is within single quote 'x'
    rather than parentheses (x). This produces clearer error messages when x
    is either ( or ). Error messages currently are:
    
    "Expected format '(real,imaginary)'"
    "Expected start '('"
    "Expected end '('"
    "Expected separator between two numbers ','"
    "Incorrect number of parts, expected only 2 using separator ','"
    "Could not parse real part 's'"
    "Could not parse imaginary part 's'"
---
 .../apache/commons/numbers/complex/Complex.java    | 36 +++++++++++-----------
 1 file changed, 18 insertions(+), 18 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 0d05f85..32319af 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
@@ -90,7 +90,7 @@ public final class Complex implements Serializable  {
      * allowing for formatting characters. The size is 64.
      */
     private static final int TO_STRING_SIZE = 64;
-    /** The minimum number of characters in the format. */
+    /** The minimum number of characters in the format. This is 5, e.g. {@code "(0,0)"}. */
     private static final int FORMAT_MIN_LEN = 5;
     /** {@link #toString() String representation}. */
     private static final char FORMAT_START = '(';
@@ -98,7 +98,7 @@ public final class Complex implements Serializable  {
     private static final char FORMAT_END = ')';
     /** {@link #toString() String representation}. */
     private static final char FORMAT_SEP = ',';
-    /** The minimum number of characters before the separator. This is 2, e.g. {@code "(1"}. */
+    /** The minimum number of characters before the separator. This is 2, e.g. {@code "(0"}. */
     private static final int BEFORE_SEP = 2;
 
     /** The imaginary part. */
@@ -234,28 +234,28 @@ public final class Complex implements Serializable  {
                 FORMAT_START + "real" + FORMAT_SEP + "imaginary" + FORMAT_END, null);
         }
 
-        // Confirm start: "^(.*"
-        final int startParen = s.indexOf(FORMAT_START);
-        if (startParen != 0) {
-            throw parsingException("Expected start string", FORMAT_START, null);
+        // Confirm start: '('
+        if (s.charAt(0) != FORMAT_START) {
+            throw parsingException("Expected start", FORMAT_START, null);
         }
 
-        // Confirm end: "^(.*)$"
-        final int endParen = s.indexOf(FORMAT_END);
-        if (endParen != len - 1) {
-            throw parsingException("Expected end string", FORMAT_END, null);
+        // Confirm end: ')'
+        if (s.charAt(len - 1) != FORMAT_END) {
+            throw parsingException("Expected end", FORMAT_END, null);
         }
 
-        // Confirm separator: "^([^,]+,[^,]+)$"
-        final int sep = s.indexOf(FORMAT_SEP, 1);
-        if (sep < BEFORE_SEP || sep == endParen - 1) {
-            throw parsingException("Expected separator string between two numbers", FORMAT_SEP, null);
+        // Confirm separator ',' is between at least 2 characters from
+        // either end: "(x,x)"
+        // Count back from the end ignoring the last 2 characters.
+        final int sep = s.lastIndexOf(FORMAT_SEP, len - 3);
+        if (sep < BEFORE_SEP) {
+            throw parsingException("Expected separator between two numbers", FORMAT_SEP, null);
         }
 
         // Should be no more separators
         if (s.indexOf(FORMAT_SEP, sep + 1) != -1) {
-            throw parsingException("Incorrect number of parts: Expected only 2",
-                "separator is '" + FORMAT_SEP + "'", null);
+            throw parsingException("Incorrect number of parts, expected only 2 using separator",
+                FORMAT_SEP, null);
         }
 
         // Try to parse the parts
@@ -268,7 +268,7 @@ public final class Complex implements Serializable  {
             throw parsingException("Could not parse real part", rePart, ex);
         }
 
-        final String imPart = s.substring(sep + 1, endParen);
+        final String imPart = s.substring(sep + 1, len - 1);
         final double im;
         try {
             im = Double.parseDouble(imPart);
@@ -2376,7 +2376,7 @@ public final class Complex implements Serializable  {
         // Not called with a null message or error
         final StringBuilder sb = new StringBuilder(100)
             .append(message)
-            .append(" (").append(error).append(" )");
+            .append(" '").append(error).append('\'');
         if (cause != null) {
             sb.append(": ").append(cause.getMessage());
         }


[commons-numbers] 03/03: Use final.

Posted by ah...@apache.org.
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 15c7706701e41db5a604f21b4582ece67f56135f
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Mon Dec 16 22:36:19 2019 +0000

    Use final.
---
 .../commons/numbers/complex/ComplexTest.java       | 124 ++++++++++-----------
 1 file changed, 61 insertions(+), 63 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 0acd90c..3a0adce 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
@@ -22,8 +22,6 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.DoubleFunction;
-import java.util.function.UnaryOperator;
-
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
 import org.junit.jupiter.api.Assertions;
@@ -236,7 +234,7 @@ public class ComplexTest {
     public void testAddReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.add(y);
+        final Complex z = x.add(y);
         Assertions.assertEquals(8.0, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -247,7 +245,7 @@ public class ComplexTest {
     public void testAddRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.add(y);
+        final Complex z = x.add(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -258,7 +256,7 @@ public class ComplexTest {
     public void testAddRealInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.add(y);
+        final Complex z = x.add(y);
         Assertions.assertEquals(inf, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -269,11 +267,11 @@ public class ComplexTest {
     public void testAddRealWithNegZeroImaginary() {
         final Complex x = Complex.ofCartesian(3.0, -0.0);
         final double y = 5.0;
-        Complex z = x.add(y);
+        final 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
-        Complex z2 = x.add(ofReal(y));
+        final Complex z2 = x.add(ofReal(y));
         Assertions.assertEquals(8.0, z2.getReal());
         Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no-sign preservation");
     }
@@ -282,7 +280,7 @@ public class ComplexTest {
     public void testAddImaginary() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.addImaginary(y);
+        final Complex z = x.addImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(9.0, z.getImaginary());
         // Equivalent
@@ -293,7 +291,7 @@ public class ComplexTest {
     public void testAddImaginaryNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.addImaginary(y);
+        final Complex z = x.addImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -304,7 +302,7 @@ public class ComplexTest {
     public void testAddImaginaryInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.addImaginary(y);
+        final Complex z = x.addImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(inf, z.getImaginary());
         // Equivalent
@@ -315,11 +313,11 @@ public class ComplexTest {
     public void testAddImaginaryWithNegZeroReal() {
         final Complex x = Complex.ofCartesian(-0.0, 4.0);
         final double y = 5.0;
-        Complex z = x.addImaginary(y);
+        final Complex z = x.addImaginary(y);
         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
-        Complex z2 = x.add(ofImaginary(y));
+        final Complex z2 = x.add(ofImaginary(y));
         Assertions.assertEquals(0.0, z2.getReal(), "Expected no-sign preservation");
         Assertions.assertEquals(9.0, z2.getImaginary());
     }
@@ -412,7 +410,7 @@ public class ComplexTest {
     public void testDivideRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.divide(y);
+        final Complex z = x.divide(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -474,7 +472,7 @@ public class ComplexTest {
     public void testDivideImaginaryNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.divideImaginary(y);
+        final Complex z = x.divideImaginary(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -608,7 +606,7 @@ public class ComplexTest {
     public void testMultiplyRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.multiply(y);
+        final Complex z = x.multiply(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -646,7 +644,7 @@ public class ComplexTest {
         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));
+        final Complex z2 = x.multiply(ofReal(-y));
         Assertions.assertEquals(-0.0, z2.getReal());
         Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no sign preservation");
     }
@@ -672,7 +670,7 @@ public class ComplexTest {
     public void testMultiplyImaginaryNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.multiplyImaginary(y);
+        final Complex z = x.multiplyImaginary(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -745,7 +743,7 @@ public class ComplexTest {
                 // Check verses algebra solution
                 Assertions.assertEquals(b, x.getReal());
                 Assertions.assertEquals(-a, x.getImaginary());
-                for (Complex negI : negIs) {
+                for (final Complex negI : negIs) {
                     final Complex z = c.multiply(negI);
                     Assertions.assertEquals(x, z);
                 }
@@ -756,14 +754,14 @@ public class ComplexTest {
     @Test
     public void testZeroMultiplyI() {
         final double[] zeros = {-0.0, 0.0};
-        for (double a : zeros) {
-            for (double b : zeros) {
+        for (final double a : zeros) {
+            for (final double b : zeros) {
                 final Complex c = Complex.ofCartesian(a, b);
-                Complex x = c.multiplyImaginary(1.0);
+                final 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);
+                final 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)
@@ -782,17 +780,17 @@ public class ComplexTest {
     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 Complex negI = Complex.I.negate();
         final double[] zeros = {-0.0, 0.0};
-        for (double a : zeros) {
-            for (double b : zeros) {
+        for (final double a : zeros) {
+            for (final double b : zeros) {
                 final Complex c = Complex.ofCartesian(a, b);
-                Complex x = c.multiplyImaginary(-1.0);
+                final 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();
+                final Complex z = c.multiply(negI);
+                final 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)
@@ -893,13 +891,13 @@ public class ComplexTest {
             BiFunction<Complex, Complex, Complex> complexOperation, long expectedFailures) {
         // With an operation on zero or non-zero arguments
         final double[] arguments = {-0.0, 0.0, -2, 3};
-        for (double a : arguments) {
-            for (double b : arguments) {
+        for (final double a : arguments) {
+            for (final double b : arguments) {
                 final Complex c = Complex.ofCartesian(a, b);
                 for (final double arg : arguments) {
-                    Complex y = doubleOperation.apply(c, arg);
-                    Complex z = complexOperation.apply(c, doubleToComplex.apply(arg));
-                    boolean expectedFailure = (expectedFailures & 0x1) == 1;
+                    final Complex y = doubleOperation.apply(c, arg);
+                    final Complex z = complexOperation.apply(c, doubleToComplex.apply(arg));
+                    final boolean expectedFailure = (expectedFailures & 0x1) == 1;
                     expectedFailures >>>= 1;
                     // Check the same answer. Sign is allowed to be different for zero.
                     Assertions.assertEquals(y.getReal(), z.getReal(), 0, () -> c + " " + name + " " + arg + ": real");
@@ -934,13 +932,13 @@ public class ComplexTest {
         long expectedFailures = 0b11001101111011001100110011001110110011110010000111001101000000L;
         // With an operation on zero or non-zero arguments
         final double[] arguments = {-0.0, 0.0, -2, 3};
-        for (double a : arguments) {
-            for (double b : arguments) {
+        for (final double a : arguments) {
+            for (final double b : arguments) {
                 final Complex c = Complex.ofCartesian(a, b);
                 for (final double arg : arguments) {
-                    Complex y = c.divideImaginary(arg);
+                    final Complex y = c.divideImaginary(arg);
                     Complex z = c.divide(ofImaginary(arg));
-                    boolean expectedFailure = (expectedFailures & 0x1) == 1;
+                    final boolean expectedFailure = (expectedFailures & 0x1) == 1;
                     expectedFailures >>>= 1;
                     // If divide by zero then the divide(Complex) method matches divide by real.
                     // To match divide by imaginary requires multiplication by I.
@@ -948,10 +946,10 @@ public class ComplexTest {
                         // Same result if multiplied by I. The sign may not match so
                         // optionally ignore the sign of the infinity.
                         z = z.multiplyImaginary(1);
-                        double ya = expectedFailure ? Math.abs(y.getReal()) : y.getReal();
-                        double yb = expectedFailure ? Math.abs(y.getImaginary()) : y.getImaginary();
-                        double za = expectedFailure ? Math.abs(z.getReal()) : z.getReal();
-                        double zb = expectedFailure ? Math.abs(z.getImaginary()) : z.getImaginary();
+                        final double ya = expectedFailure ? Math.abs(y.getReal()) : y.getReal();
+                        final double yb = expectedFailure ? Math.abs(y.getImaginary()) : y.getImaginary();
+                        final double za = expectedFailure ? Math.abs(z.getReal()) : z.getReal();
+                        final double zb = expectedFailure ? Math.abs(z.getImaginary()) : z.getImaginary();
                         Assertions.assertEquals(ya, za, () -> c + " divideImaginary " + arg + ": real");
                         Assertions.assertEquals(yb, zb, () -> c + " divideImaginary " + arg + ": imaginary");
                     } else {
@@ -1005,7 +1003,7 @@ public class ComplexTest {
     public void testSubtractReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtract(y);
+        final Complex z = x.subtract(y);
         Assertions.assertEquals(-2.0, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -1016,7 +1014,7 @@ public class ComplexTest {
     public void testSubtractRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.subtract(y);
+        final Complex z = x.subtract(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -1027,7 +1025,7 @@ public class ComplexTest {
     public void testSubtractRealInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.subtract(y);
+        final Complex z = x.subtract(y);
         Assertions.assertEquals(-inf, z.getReal());
         Assertions.assertEquals(4.0, z.getImaginary());
         // Equivalent
@@ -1038,7 +1036,7 @@ public class ComplexTest {
     public void testSubtractRealWithNegZeroImaginary() {
         final Complex x = Complex.ofCartesian(3.0, -0.0);
         final double y = 5.0;
-        Complex z = x.subtract(y);
+        final Complex z = x.subtract(y);
         Assertions.assertEquals(-2.0, z.getReal());
         Assertions.assertEquals(-0.0, z.getImaginary());
         // Equivalent
@@ -1050,7 +1048,7 @@ public class ComplexTest {
     public void testSubtractImaginary() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtractImaginary(y);
+        final Complex z = x.subtractImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(-1.0, z.getImaginary());
         // Equivalent
@@ -1061,7 +1059,7 @@ public class ComplexTest {
     public void testSubtractImaginaryNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.subtractImaginary(y);
+        final Complex z = x.subtractImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -1072,7 +1070,7 @@ public class ComplexTest {
     public void testSubtractImaginaryInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.subtractImaginary(y);
+        final Complex z = x.subtractImaginary(y);
         Assertions.assertEquals(3.0, z.getReal());
         Assertions.assertEquals(-inf, z.getImaginary());
         // Equivalent
@@ -1083,7 +1081,7 @@ public class ComplexTest {
     public void testSubtractImaginaryWithNegZeroReal() {
         final Complex x = Complex.ofCartesian(-0.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtractImaginary(y);
+        final Complex z = x.subtractImaginary(y);
         Assertions.assertEquals(-0.0, z.getReal());
         Assertions.assertEquals(-1.0, z.getImaginary());
         // Equivalent
@@ -1095,7 +1093,7 @@ public class ComplexTest {
     public void testSubtractFromReal() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtractFrom(y);
+        final Complex z = x.subtractFrom(y);
         Assertions.assertEquals(2.0, z.getReal());
         Assertions.assertEquals(-4.0, z.getImaginary());
         // Equivalent
@@ -1106,7 +1104,7 @@ public class ComplexTest {
     public void testSubtractFromRealNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.subtractFrom(y);
+        final Complex z = x.subtractFrom(y);
         Assertions.assertEquals(nan, z.getReal());
         Assertions.assertEquals(-4.0, z.getImaginary());
         // Equivalent
@@ -1117,7 +1115,7 @@ public class ComplexTest {
     public void testSubtractFromRealInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.subtractFrom(y);
+        final Complex z = x.subtractFrom(y);
         Assertions.assertEquals(inf, z.getReal());
         Assertions.assertEquals(-4.0, z.getImaginary());
         // Equivalent
@@ -1128,7 +1126,7 @@ public class ComplexTest {
     public void testSubtractFromRealWithPosZeroImaginary() {
         final Complex x = Complex.ofCartesian(3.0, 0.0);
         final double y = 5.0;
-        Complex z = x.subtractFrom(y);
+        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
@@ -1139,7 +1137,7 @@ public class ComplexTest {
     public void testSubtractFromImaginary() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtractFromImaginary(y);
+        final Complex z = x.subtractFromImaginary(y);
         Assertions.assertEquals(-3.0, z.getReal());
         Assertions.assertEquals(1.0, z.getImaginary());
         // Equivalent
@@ -1150,7 +1148,7 @@ public class ComplexTest {
     public void testSubtractFromImaginaryNaN() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = nan;
-        Complex z = x.subtractFromImaginary(y);
+        final Complex z = x.subtractFromImaginary(y);
         Assertions.assertEquals(-3.0, z.getReal());
         Assertions.assertEquals(nan, z.getImaginary());
         // Equivalent
@@ -1161,7 +1159,7 @@ public class ComplexTest {
     public void testSubtractFromImaginaryInf() {
         final Complex x = Complex.ofCartesian(3.0, 4.0);
         final double y = inf;
-        Complex z = x.subtractFromImaginary(y);
+        final Complex z = x.subtractFromImaginary(y);
         Assertions.assertEquals(-3.0, z.getReal());
         Assertions.assertEquals(inf, z.getImaginary());
         // Equivalent
@@ -1172,7 +1170,7 @@ public class ComplexTest {
     public void testSubtractFromImaginaryWithPosZeroReal() {
         final Complex x = Complex.ofCartesian(0.0, 4.0);
         final double y = 5.0;
-        Complex z = x.subtractFromImaginary(y);
+        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
@@ -1335,7 +1333,7 @@ public class ComplexTest {
         };
         final ArrayList<Complex> list = createCombinations(values);
 
-        for (Complex c : list) {
+        for (final Complex c : list) {
             final double real = c.getReal();
             final double imag = c.getImaginary();
 
@@ -1413,7 +1411,7 @@ public class ComplexTest {
 
         final String msg = "'equals' not compatible with 'hashCode'";
 
-        for (Complex c : list) {
+        for (final Complex c : list) {
             final double real = c.getReal();
             final double imag = c.getImaginary();
             final int expected = Arrays.hashCode(new double[] {real, imag});
@@ -1434,8 +1432,8 @@ public class ComplexTest {
             Assertions.assertNotEquals(real, realDelta, "Real was not changed");
             Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
 
-            Complex cRealDelta = Complex.ofCartesian(realDelta, imag);
-            Complex cImagDelta = Complex.ofCartesian(real, imagDelta);
+            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);
             }
@@ -1481,8 +1479,8 @@ public class ComplexTest {
      */
     private static ArrayList<Complex> createCombinations(final double[] values) {
         final ArrayList<Complex> list = new ArrayList<>(values.length * values.length);
-        for (double re : values) {
-            for (double im : values) {
+        for (final double re : values) {
+            for (final double im : values) {
                 list.add(Complex.ofCartesian(re, im));
             }
         }


[commons-numbers] 02/03: Improve documentation of sign differences in +/-0.0 for arithmetic.

Posted by ah...@apache.org.
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 a4b33d4cc717cef2cc4a5be81596da9f5cdbcc03
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Mon Dec 16 22:34:56 2019 +0000

    Improve documentation of sign differences in +/-0.0 for arithmetic.
    
    Adds a comprehensive test to demonstrate cases where sign differences
    occur. These cases are non-trivial to document and the javadoc for the
    class has been left without explicit edge case documentation.
    
    In most cases the result has the same value except the sign of zero.
    
    The exception is divideImaginary(0). The equivalent case
    divide(Complex.ZERO) has edge case correction to match divide by a
    real-only zero. A separate test for divideImaginary demonstrates the
    result is made the same (except sign differences on infinities) by
    multiplying by I.
---
 .../apache/commons/numbers/complex/Complex.java    |  59 +++++---
 .../commons/numbers/complex/ComplexTest.java       | 161 ++++++++++++++++++++-
 2 files changed, 195 insertions(+), 25 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 32319af..3d5f264 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
@@ -505,12 +505,16 @@ public final class Complex implements Serializable  {
      * standard G.5.1. Method is fully in accordance with
      * C++11 standards for complex numbers.</p>
      *
+     * <p>Note: In the event of divide by zero this method produces the same result
+     * as dividing by a real-only zero using {@link #divide(double)}.
+     *
      * @param re1 Real component of first number.
      * @param im1 Imaginary component of first number.
      * @param re2 Real component of second number.
      * @param im2 Imaginary component of second number.
      * @return (a + i b) / (c + i d).
      * @see <a href="http://mathworld.wolfram.com/ComplexDivision.html">Complex Division</a>
+     * @see #divide(double)
      */
     private static Complex divide(double re1, double im1, double re2, double im2) {
         double a = re1;
@@ -539,6 +543,8 @@ public final class Complex implements Serializable  {
             if ((denom == 0.0) &&
                     (!Double.isNaN(a) || !Double.isNaN(b))) {
                 // nonzero/zero
+                // This case produces the same result as divide by a real-only zero
+                // using divide(+/-0.0).
                 x = Math.copySign(Double.POSITIVE_INFINITY, c) * a;
                 y = Math.copySign(Double.POSITIVE_INFINITY, c) * b;
             } else if ((Double.isInfinite(a) || Double.isInfinite(b)) &&
@@ -616,11 +622,12 @@ public final class Complex implements Serializable  {
      * <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.
+     * <p>Note: This method should be preferred over using
+     * {@link #divide(Complex) divide(Complex.ofCartesian(divisor, 0))}. Division
+     * can generate signed zeros if {@code this} complex has zeros for the real
+     * and/or imaginary component, or the divisor is infinity. The summation of signed zeros
+     * in {@link #divide(Complex)} may create zeros in the result that differ in sign
+     * from the equivalent call to divide by a real-only number.
      *
      * @param  divisor Value by which this {@code Complex} is to be divided.
      * @return {@code this / divisor}.
@@ -642,15 +649,25 @@ public final class Complex implements Serializable  {
      * <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.
+     * <p>Note: This method should be preferred over using
+     * {@link #divide(Complex) divide(Complex.ofCartesian(0, divisor))}. Division
+     * can generate signed zeros if {@code this} complex has zeros for the real
+     * and/or imaginary component, or the divisor is infinity. The summation of signed zeros
+     * in {@link #divide(Complex)} may create zeros in the result that differ in sign
+     * from the equivalent call to divide by an imaginary-only number.
+     *
+     * <p>Warning: This method will generate a different result from
+     * {@link #divide(Complex) divide(Complex.ofCartesian(0, divisor))} if the divisor is zero.
+     * In this case the divide method using a zero-valued Complex will produce the same result
+     * as dividing by a real-only zero. The output from dividing by imaginary zero will create
+     * infinite and NaN values in the same component parts as the output from
+     * {@code this.divide(Complex.ZERO).multiplyImaginary(1)}, however the sign
+     * of some infinity values may be negated.
      *
      * @param  divisor Value by which this {@code Complex} is to be divided.
      * @return {@code this / divisor}.
      * @see #divide(Complex)
+     * @see #divide(double)
      */
     public Complex divideImaginary(double divisor) {
         return new Complex(imaginary / divisor, -real / divisor);
@@ -1039,11 +1056,12 @@ public final class Complex implements Serializable  {
      * <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.
+     * <p>Note: This method should be preferred over using
+     * {@link #multiply(Complex) multiply(Complex.ofCartesian(factor, 0))}. Multiplication
+     * can generate signed zeros if either {@code this} complex has zeros for the real
+     * and/or imaginary component, or if the factor is zero. The summation of signed zeros
+     * in {@link #multiply(Complex)} may create zeros in the result that differ in sign
+     * from the equivalent call to multiply by a real-only number.
      *
      * @param  factor value to be multiplied by this {@code Complex}.
      * @return {@code this * factor}.
@@ -1074,11 +1092,12 @@ public final class Complex implements Serializable  {
      * <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.
+     * <p>Note: This method should be preferred over using
+     * {@link #multiply(Complex) multiply(Complex.ofCartesian(0, factor))}. Multiplication
+     * can generate signed zeros if either {@code this} complex has zeros for the real
+     * and/or imaginary component, or if the factor is zero. The summation of signed zeros
+     * in {@link #multiply(Complex)} may create zeros in the result that differ in sign
+     * from the equivalent call to multiply by an imaginary-only number.
      *
      * @param  factor value to be multiplied by this {@code Complex}.
      * @return {@code this * factor}.
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 c9c0ee1..0acd90c 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
@@ -20,6 +20,9 @@ package org.apache.commons.numbers.complex;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.DoubleFunction;
+import java.util.function.UnaryOperator;
 
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
@@ -808,11 +811,159 @@ public class ComplexTest {
         }
     }
 
-    // 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.
+    /**
+     * Arithmetic test using combinations of +/- x for real, imaginary and
+     * and the double argument for add, subtract, subtractFrom, multiply and divide,
+     * where x is zero or non-zero.
+     *
+     * <p>The differences to the same argument as a Complex are tested. The only differences
+     * should be the sign of zero in certain cases.
+     */
+    @Test
+    public void testSignedArithmetic() {
+        // The following lists the conditions for the double primitive operation where
+        // the Complex operation is different. Here the double argument can be:
+        // x   : any value
+        // +x  : positive
+        // +0.0: positive zero
+        // -x  : negative
+        // -0.0: negative zero
+        // 0   : any zero
+        // use y for any non-zero value
+
+        // Check the known fail cases using an integer as a bit set.
+        // If a bit is 1 then the case is known to fail.
+        // The 64 cases are enumerated as:
+        // 4 cases: (a,-0.0) operation on -0.0, 0.0, -2, 3
+        // 4 cases: (a, 0.0) operation on -0.0, 0.0, -2, 3
+        // 4 cases: (a,-2.0) operation on -0.0, 0.0, -2, 3
+        // 4 cases: (a, 3.0) operation on -0.0, 0.0, -2, 3
+        // with a in [-0.0, 0.0, -2, 3]
+        // The least significant bit is for the first case.
+
+        // The bit set was generated for this test. The summary below demonstrates
+        // documenting the sign change cases for multiply and divide is non-trivial
+        // and the javadoc in Complex does not break down the actual cases.
+
+        // 16: (x,-0.0) + x
+        assertSignedZeroArithmetic("addReal", Complex::add, ComplexTest::ofReal, Complex::add, 0b1111000000000000111100000000000011110000000000001111L);
+        // 16: (-0.0,x) + x
+        assertSignedZeroArithmetic("addImaginary", Complex::addImaginary, ComplexTest::ofImaginary, Complex::add, 0b1111111111111111L);
+        // 0:
+        assertSignedZeroArithmetic("subtractReal", Complex::subtract, ComplexTest::ofReal, Complex::subtract, 0);
+        // 0:
+        assertSignedZeroArithmetic("subtractImaginary", Complex::subtractImaginary, ComplexTest::ofImaginary, Complex::subtract, 0);
+        // 16: x - (x,+0.0)
+        assertSignedZeroArithmetic("subtractFromReal", Complex::subtractFrom, ComplexTest::ofReal, (y, z) -> z.subtract(y), 0b11110000000000001111000000000000111100000000000011110000L);
+        // 16: x - (+0.0,x)
+        assertSignedZeroArithmetic("subtractFromImaginary", Complex::subtractFromImaginary, ComplexTest::ofImaginary, (y, z) -> z.subtract(y), 0b11111111111111110000000000000000L);
+        // 4: (-0.0,-x) * +x
+        // 4: (+0.0,-0.0) * x
+        // 4: (+0.0,x) * -x
+        // 2: (-y,-x) * +0.0
+        // 2: (+y,+0.0) * -x
+        // 2: (+y,-0.0) * +x
+        // 2: (+y,-x) * -0.0
+        // 2: (+x,-y) * +0.0
+        // 2: (+x,+y) * -0.0
+        assertSignedZeroArithmetic("multiplyReal", Complex::multiply, ComplexTest::ofReal, Complex::multiply, 0b1001101011011000000100000001000010111010111110000101000001010L);
+        // 4: (-0.0,+x) * +x
+        // 2: (+0.0,-0.0) * -x
+        // 4: (+0.0,+0.0) * x
+        // 2: (+0.0,+y) * -x
+        // 2: (-y,+x) * +0.0
+        // 4: (+y,x) * -0.0
+        // 2: (+0.0,+/-y) * -/+0
+        // 2: (+y,+/-0.0) * +/-y  (sign 0.0 matches sign y)
+        // 2: (+y,+x) * +0.0
+        assertSignedZeroArithmetic("multiplyImaginary", Complex::multiplyImaginary, ComplexTest::ofImaginary, Complex::multiply, 0b11000110110101001000000010000001110001111101011010000010100000L);
+        // 2: (-0.0,0) / +y
+        // 2: (+0.0,+x) / -y
+        // 2: (-x,0) / -y
+        // 1: (-0.0,+y) / +y
+        // 1: (-y,+0.0) / -y
+        assertSignedZeroArithmetic("divideReal", Complex::divide, ComplexTest::ofReal, Complex::divide, 0b100100001000000010000001000000011001000L);
+
+        // DivideImaginary has its own test as the result is not always equal ignoring the sign.
+    }
+
+    private static void assertSignedZeroArithmetic(String name,
+            BiFunction<Complex, Double, Complex> doubleOperation,
+            DoubleFunction<Complex> doubleToComplex,
+            BiFunction<Complex, Complex, Complex> complexOperation, long expectedFailures) {
+        // With an operation on zero or non-zero arguments
+        final double[] arguments = {-0.0, 0.0, -2, 3};
+        for (double a : arguments) {
+            for (double b : arguments) {
+                final Complex c = Complex.ofCartesian(a, b);
+                for (final double arg : arguments) {
+                    Complex y = doubleOperation.apply(c, arg);
+                    Complex z = complexOperation.apply(c, doubleToComplex.apply(arg));
+                    boolean expectedFailure = (expectedFailures & 0x1) == 1;
+                    expectedFailures >>>= 1;
+                    // Check the same answer. Sign is allowed to be different for zero.
+                    Assertions.assertEquals(y.getReal(), z.getReal(), 0, () -> c + " " + name + " " + arg + ": real");
+                    Assertions.assertEquals(y.getImaginary(), z.getImaginary(), 0, () -> c + " " + name + " " + arg + ": imaginary");
+                    Assertions.assertEquals(expectedFailure, !y.equals(z), () -> c + " " + name + " " + arg + ": sign-difference");
+                }
+            }
+        }
+    }
+
+    /**
+     * Arithmetic test using combinations of +/- x for real, imaginary and
+     * and the double argument for divideImaginary,
+     * where x is zero or non-zero.
+     *
+     * <p>The differences to the same argument as a Complex are tested. This checks for sign
+     * differences of zero or, if divide by zero, that the result is equal
+     * to divide by zero using a Complex then multiplied by I.
+     */
+    @Test
+    public void testDivideImaginaryArithmetic() {
+        // Cases for divide by non-zero:
+        // 2: (-0.0,+x) / -y
+        // 4: (+x,+/-0.0) / -/+y
+        // 2: (+0.0,+x) / +y
+        // Cases for divide by zero after multiplication of the Complex result by I:
+        // 2: (-0.0,+/-y) / +0.0
+        // 2: (+0.0,+/-y) / +0.0
+        // 4: (-y,x) / +0.0
+        // 4: (y,x) / +0.0
+        // If multiplied by -I all the divide by -0.0 cases have sign errors and / +0.0 is OK.
+        long expectedFailures = 0b11001101111011001100110011001110110011110010000111001101000000L;
+        // With an operation on zero or non-zero arguments
+        final double[] arguments = {-0.0, 0.0, -2, 3};
+        for (double a : arguments) {
+            for (double b : arguments) {
+                final Complex c = Complex.ofCartesian(a, b);
+                for (final double arg : arguments) {
+                    Complex y = c.divideImaginary(arg);
+                    Complex z = c.divide(ofImaginary(arg));
+                    boolean expectedFailure = (expectedFailures & 0x1) == 1;
+                    expectedFailures >>>= 1;
+                    // If divide by zero then the divide(Complex) method matches divide by real.
+                    // To match divide by imaginary requires multiplication by I.
+                    if (arg == 0) {
+                        // Same result if multiplied by I. The sign may not match so
+                        // optionally ignore the sign of the infinity.
+                        z = z.multiplyImaginary(1);
+                        double ya = expectedFailure ? Math.abs(y.getReal()) : y.getReal();
+                        double yb = expectedFailure ? Math.abs(y.getImaginary()) : y.getImaginary();
+                        double za = expectedFailure ? Math.abs(z.getReal()) : z.getReal();
+                        double zb = expectedFailure ? Math.abs(z.getImaginary()) : z.getImaginary();
+                        Assertions.assertEquals(ya, za, () -> c + " divideImaginary " + arg + ": real");
+                        Assertions.assertEquals(yb, zb, () -> c + " divideImaginary " + arg + ": imaginary");
+                    } else {
+                        // Check the same answer. Sign is allowed to be different for zero.
+                        Assertions.assertEquals(y.getReal(), z.getReal(), 0, () -> c + " divideImaginary " + arg + ": real");
+                        Assertions.assertEquals(y.getImaginary(), z.getImaginary(), 0, () -> c + " divideImaginary " + arg + ": imaginary");
+                        Assertions.assertEquals(expectedFailure, !y.equals(z), () -> c + " divideImaginary " + arg + ": sign-difference");
+                    }
+                }
+            }
+        }
+    }
 
     @Test
     public void testNegate() {