aherbert commented on code in PR #117:
URL: https://github.com/apache/commons-numbers/pull/117#discussion_r924732640
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java:
##########
@@ -400,4 +401,28 @@ private static String preprocessTestData(String line,
TestDataFlagOption option,
}
return line;
}
+
+ /**
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param c The input complex number.
+ * @param operation1 the operation on the Complex object
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param name the operation name
+ * @return the resulting complex number from the given operation
+ */
+ static Complex assertSame(Complex c,
+ UnaryOperator<Complex> operation1,
+ ComplexUnaryOperator<ComplexNumber> operation2,
+ String name) {
+ final Complex z = operation1.apply(c);
+ // Test operation2 produces the exact same result
+ operation2.apply(c.real(), c.imag(), (x, y) -> {
Review Comment:
This should actually create a ComplexNumber since we wish to test the object
created is returned by the function:
```Java
final ComplexNumber z2 = operation2.apply(c.real(), c.imag(),
ComplexNumber::new);
Assertions.assertEquals(z.real(), z2.real(), () -> "UnaryOperator " + name +
" real");
Assertions.assertEquals(z.imag(), z2.imag(), () -> "UnaryOperator " + name +
" imaginary");
```
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java:
##########
@@ -400,4 +401,28 @@ private static String preprocessTestData(String line,
TestDataFlagOption option,
}
return line;
}
+
+ /**
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param c The input complex number.
+ * @param operation1 the operation on the Complex object
Review Comment:
Drop the `the`, use capitalisation and end with a period:
```
Operation on the Complex object.
```
Same with the other tags.
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java:
##########
@@ -169,13 +170,11 @@ static void assertComplex(Complex c,
Complex expected, long maxUlps) {
final Complex z = operation1.apply(c);
Review Comment:
Replace with:
```Java
final Complex z = TestUtils.assertSame(c, operation1, operation2, name);
```
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java:
##########
@@ -145,6 +145,38 @@ static void assertEquals(Supplier<String> msg, double
expected, double actual, l
}
}
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal within the provided units of least
+ * precision. The maximum count of numbers allowed between the two values
is
+ * {@code maxUlps - 1}.
+ *
+ * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param c Input number.
+ * @param name the operation name
+ * @param operation1 the operation on the Complex object
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param expected Expected result.
+ */
+ static void assertComplex(Complex c,
+ String name,
+ UnaryOperator<Complex> operation1,
+ ComplexUnaryOperator<ComplexNumber> operation2,
+ Complex expected, long maxUlps) {
+
+ final Complex z = operation1.apply(c);
+
+ assertEquals(() -> "UnaryOperator " + name + "(" + c + "): real",
expected.real(), z.real(), maxUlps);
+ assertEquals(() -> "UnaryOperator " + name + "(" + c + "): imaginary",
expected.imag(), z.imag(), maxUlps);
+
+ TestUtils.assertSame(c, operation1, operation2, name);
Review Comment:
Redundant when moved to the top
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java:
##########
@@ -81,6 +81,57 @@ private static void assertComplex(double a, double b,
CReferenceTest.assertComplex(c, name, operation, e, maxUlps);
}
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal if there are no floating-point
values between them.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
Review Comment:
Missing `<p>`
##########
commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java:
##########
@@ -581,4 +583,263 @@ private static double x2y2(double x, double y) {
// and reducing error to < 0.5 ulp for the final sqrt.
return xx - r + yy + yyLow + xxLow + r;
}
+
+ /**
+ * Compute {@code x^2 + y^2 - 1} in high precision.
+ * Assumes that the values x and y can be multiplied without overflow; that
+ * {@code x >= y}; and both values are positive.
+ *
+ * @param x the x value
+ * @param y the y value
+ * @return {@code x^2 + y^2 - 1}.
+ */
+ //TODO - make it private in future
+ static double x2y2m1(double x, double y) {
+ // Hull et al used (x-1)*(x+1)+y*y.
+ // From the paper on page 236:
+
+ // If x == 1 there is no cancellation.
+
+ // If x > 1, there is also no cancellation, but the argument is now
accurate
+ // only to within a factor of 1 + 3 EPSILSON (note that x – 1 is
exact),
+ // so that error = 3 EPSILON.
+
+ // If x < 1, there can be serious cancellation:
+
+ // If 4 y^2 < |x^2 – 1| the cancellation is not serious ... the
argument is accurate
+ // only to within a factor of 1 + 4 EPSILSON so that error = 4 EPSILON.
+
+ // Otherwise there can be serious cancellation and the relative error
in the real part
+ // could be enormous.
+
+ final double xx = x * x;
+ final double yy = y * y;
+ // Modify to use high precision before the threshold set by Hull et al.
+ // This is to preserve the monotonic output of the computation at the
switch.
+ // Set the threshold when x^2 + y^2 is above 0.5 thus subtracting 1
results in a number
+ // that can be expressed with a higher precision than any number in
the range 0.5-1.0
+ // due to the variable exponent used below 0.5.
+ if (x < 1 && xx + yy > 0.5) {
+ // Large relative error.
+ // This does not use o.a.c.numbers.LinearCombination.value(x, x,
y, y, 1, -1).
+ // It is optimised knowing that:
+ // - the products are squares
+ // - the final term is -1 (which does not require split
multiplication and addition)
+ // - The answer will not be NaN as the terms are not NaN components
+ // - The order is known to be 1 > |x| >= |y|
+ // The squares are computed using a split multiply algorithm and
+ // the summation using an extended precision summation algorithm.
+
+ // Split x and y as one 26 bits number and one 27 bits number
+ final double xHigh = splitHigh(x);
+ final double xLow = x - xHigh;
+ final double yHigh = splitHigh(y);
+ final double yLow = y - yHigh;
+
+ // Accurate split multiplication x * x and y * y
+ final double x2Low = squareLow(xLow, xHigh, xx);
+ final double y2Low = squareLow(yLow, yHigh, yy);
+
+ return sumx2y2m1(xx, x2Low, yy, y2Low);
+ }
+ return (x - 1) * (x + 1) + yy;
+ }
+
+ /**
+ * Implement Dekker's method to split a value into two parts. Multiplying
by (2^s + 1) create
+ * a big value from which to derive the two split parts.
+ * <pre>
+ * c = (2^s + 1) * a
+ * a_big = c - a
+ * a_hi = c - a_big
+ * a_lo = a - a_hi
+ * a = a_hi + a_lo
+ * </pre>
+ *
+ * <p>The multiplicand must be odd allowing a p-bit value to be split into
+ * (p-s)-bit value {@code a_hi} and a non-overlapping (s-1)-bit value
{@code a_lo}.
+ * Combined they have (p-1) bits of significand but the sign bit of
{@code a_lo}
+ * contains a bit of information.
+ *
+ * @param a Value.
+ * @return the high part of the value.
+ * @see <a href="https://doi.org/10.1007/BF01397083">
+ * Dekker (1971) A floating-point technique for extending the available
precision</a>
+ */
+ static double splitHigh(double a) {
Review Comment:
Should be private. Or add a TODO to make private.
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java:
##########
@@ -81,6 +81,57 @@ private static void assertComplex(double a, double b,
CReferenceTest.assertComplex(c, name, operation, e, maxUlps);
}
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal if there are no floating-point
values between them.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param a Real part.
+ * @param b Imaginary part.
+ * @param name The operation name.
+ * @param operation1 the operation on the Complex object.
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param x Expected real part.
+ * @param y Expected imaginary part.
+ */
+ private static void assertComplex(double a, double b,
+ String name, UnaryOperator<Complex>
operation1,
+ ComplexUnaryOperator<ComplexNumber>
operation2,
+ double x, double y) {
+ assertComplex(a, b, name, operation1, operation2, x, y, 1);
+ }
+
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal within the provided units of least
+ * precision. The maximum count of numbers allowed between the two values
is
+ * {@code maxUlps - 1}.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
Review Comment:
Missing `<p>`
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexNumber.java:
##########
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.complex;
+
+/**
+ * Cartesian representation of a complex number. The complex number is
expressed
+ * in the form \( a + ib \) where \( a \) and \( b \) are real numbers and \(
i \)
+ * is the imaginary unit which satisfies the equation \( i^2 = -1 \). For the
+ * complex number \( a + ib \), \( a \) is called the <em>real part</em> and
+ * \( b \) is called the <em>imaginary part</em>.
+ */
+class ComplexNumber {
+
+ /** The real part. */
+ private final double real;
+ /** The imaginary part. */
+ private final double imaginary;
+
+ /**
+ * Constructor representing a complex number by its real and imaginary
parts.
+ * Takes in real and imaginary and sets it to this complex number's real
and imaginary
+ *
+ * @param real Real part \( a \) of the complex number \( (a +ib \).
+ * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib
\).
+ *
+ */
+ ComplexNumber(double real, double imaginary) {
+ this.real = real;
+ this.imaginary = imaginary;
+ }
+
+ /**
+ * Creates a conjugated complex number given the real and imaginary parts,
+ * that is for the argument (a + ib), returns (a - ib).
+ *
+ * @param real Real part \( a \) of the complex number \( (a +ib \).
+ * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib
\).
+ * @return {@code ComplexNumber} object.
Review Comment:
`@return conjugated complex number`
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java:
##########
@@ -387,4 +401,28 @@ private static String preprocessTestData(String line,
TestDataFlagOption option,
}
return line;
}
+
+ /**
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param c The input complex number.
+ * @param operation1 the operation on the Complex object
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param name the operation name
+ * @return the resulting complex number from the given operation
+ */
+ static Complex assertSame(Complex c,
+ UnaryOperator<Complex> operation1,
Review Comment:
Indent 1 more space
##########
commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexSink.java:
##########
@@ -18,22 +18,22 @@
package org.apache.commons.numbers.complex;
/**
- * Represents a Terminating consumer for the complex result, used to construct
a result of type {@code R}.
- * The operation may return an object representation of the complex result.
+ * Represents a data sink for a complex number \( (a + i b) \)
Review Comment:
Period at the end of a sentence.
##########
commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java:
##########
@@ -204,16 +206,16 @@ public static boolean isInfinite(double real, double
imaginary) {
* ACM Transactions on Mathematical Software, Vol 20, No 2, pp 215-244.
* </blockquote>
*
- * @param real Real part \( a \) of the complex number \(a +ib \).
- * @param imaginary Imaginary part \( b \) of the complex number \(a +ib
\).
- * @param constructor Terminating consumer for the complex result, used to
construct a result of type {@code R}.
- * @param <R> the object type produced by the supplied constructor.
- * @return The natural logarithm of the complex number.
+ * @param real Real part \( a \) of the complex number \( (a +ib \).
+ * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib
\).
+ * @param action Consumer for the natural logarithm of the complex number.
+ * @param <R> the object taken by the supplied action.
Review Comment:
Perhaps change to `the return type of the supplied action`
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java:
##########
@@ -300,6 +300,107 @@ private static void assertConjugateEquality(Complex z,
}
}
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal. This includes the sign of zero.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * <h2>ISO C99 equalities</h2>
+ *
+ * <p>Note that this method currently enforces the conjugate equalities
for some cases
+ * where the sign of the real/imaginary parts are unspecified in ISO C99.
This is
+ * allowed (since they are unspecified). The sign specification is
appropriately
+ * handled during testing of odd/even functions. There are some functions
where it
+ * is not possible to satisfy the conjugate equality and also the odd/even
rule.
+ * The compromise made here is to satisfy only one and the other is
allowed to fail
+ * only on the sign of the output result. Known functions where this
applies are:
+ *
+ * <ul>
+ * <li>asinh(NaN, inf)
+ * <li>atanh(NaN, inf)
+ * <li>cosh(NaN, 0.0)
+ * <li>sinh(inf, inf)
+ * <li>sinh(inf, nan)
+ * </ul>
+ *
+ * @param operation the operation
+ */
+ private static void assertConjugateEquality(UnaryOperator<Complex>
operation,
+
ComplexUnaryOperator<ComplexNumber> operation2) {
+ // Edge cases. Inf/NaN are specifically handled in the C99 test cases
+ // but are repeated here to enforce the conjugate equality even when
the C99
+ // standard does not specify a sign. This may be revised in the future.
+ final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1,
+ Double.POSITIVE_INFINITY, Double.NaN};
+ for (final double x : parts) {
+ for (final double y : parts) {
+ // No conjugate for imaginary NaN
+ if (!Double.isNaN(y)) {
+ assertConjugateEquality(complex(x, y), operation,
operation2, UnspecifiedSign.NONE);
+ }
+ }
+ }
+ // Random numbers
+ final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create();
+ for (int i = 0; i < 100; i++) {
+ final double x = next(rng);
+ final double y = next(rng);
+ assertConjugateEquality(complex(x, y), operation, operation2,
UnspecifiedSign.NONE);
+ }
+ }
+
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal; the sign of the complex number is
first processed
+ * using the provided sign specification.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param z the complex number
+ * @param operation1 the operation on the Complex object
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param sign the sign specification
+ */
+ private static void assertConjugateEquality(Complex z,
+ UnaryOperator<Complex> operation1,
+ ComplexUnaryOperator<ComplexNumber> operation2,
+ UnspecifiedSign sign) {
+
+ final Complex zConj = z.conj();
+ final Complex c1 = operation1.apply(zConj);
Review Comment:
Replace with:
```Java
final Complex c1 = TestUtils.assertSame(zConj, operation1, operation2, "");
final Complex c2 = TestUtils.assertSame(z, operation1, operation2,
"").conj();
```
Then you do not need to generate ComplexNumber cn1 and cn2.
This possibly requires a name argument. It is missing from the original
tests. I cannot recall why and so can you look into adding it so we know the
conjugate equality that failed.
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java:
##########
@@ -300,6 +300,107 @@ private static void assertConjugateEquality(Complex z,
}
}
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal. This includes the sign of zero.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
Review Comment:
Missing `<p>`
##########
commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java:
##########
@@ -581,4 +583,263 @@ private static double x2y2(double x, double y) {
// and reducing error to < 0.5 ulp for the final sqrt.
return xx - r + yy + yyLow + xxLow + r;
}
+
+ /**
+ * Compute {@code x^2 + y^2 - 1} in high precision.
+ * Assumes that the values x and y can be multiplied without overflow; that
+ * {@code x >= y}; and both values are positive.
+ *
+ * @param x the x value
+ * @param y the y value
+ * @return {@code x^2 + y^2 - 1}.
+ */
+ //TODO - make it private in future
+ static double x2y2m1(double x, double y) {
+ // Hull et al used (x-1)*(x+1)+y*y.
+ // From the paper on page 236:
+
+ // If x == 1 there is no cancellation.
+
+ // If x > 1, there is also no cancellation, but the argument is now
accurate
+ // only to within a factor of 1 + 3 EPSILSON (note that x – 1 is
exact),
+ // so that error = 3 EPSILON.
+
+ // If x < 1, there can be serious cancellation:
+
+ // If 4 y^2 < |x^2 – 1| the cancellation is not serious ... the
argument is accurate
+ // only to within a factor of 1 + 4 EPSILSON so that error = 4 EPSILON.
+
+ // Otherwise there can be serious cancellation and the relative error
in the real part
+ // could be enormous.
+
+ final double xx = x * x;
+ final double yy = y * y;
+ // Modify to use high precision before the threshold set by Hull et al.
+ // This is to preserve the monotonic output of the computation at the
switch.
+ // Set the threshold when x^2 + y^2 is above 0.5 thus subtracting 1
results in a number
+ // that can be expressed with a higher precision than any number in
the range 0.5-1.0
+ // due to the variable exponent used below 0.5.
+ if (x < 1 && xx + yy > 0.5) {
+ // Large relative error.
+ // This does not use o.a.c.numbers.LinearCombination.value(x, x,
y, y, 1, -1).
+ // It is optimised knowing that:
+ // - the products are squares
+ // - the final term is -1 (which does not require split
multiplication and addition)
+ // - The answer will not be NaN as the terms are not NaN components
+ // - The order is known to be 1 > |x| >= |y|
+ // The squares are computed using a split multiply algorithm and
+ // the summation using an extended precision summation algorithm.
+
+ // Split x and y as one 26 bits number and one 27 bits number
+ final double xHigh = splitHigh(x);
+ final double xLow = x - xHigh;
+ final double yHigh = splitHigh(y);
+ final double yLow = y - yHigh;
+
+ // Accurate split multiplication x * x and y * y
+ final double x2Low = squareLow(xLow, xHigh, xx);
+ final double y2Low = squareLow(yLow, yHigh, yy);
+
+ return sumx2y2m1(xx, x2Low, yy, y2Low);
+ }
+ return (x - 1) * (x + 1) + yy;
+ }
+
+ /**
+ * Implement Dekker's method to split a value into two parts. Multiplying
by (2^s + 1) create
+ * a big value from which to derive the two split parts.
+ * <pre>
+ * c = (2^s + 1) * a
+ * a_big = c - a
+ * a_hi = c - a_big
+ * a_lo = a - a_hi
+ * a = a_hi + a_lo
+ * </pre>
+ *
+ * <p>The multiplicand must be odd allowing a p-bit value to be split into
+ * (p-s)-bit value {@code a_hi} and a non-overlapping (s-1)-bit value
{@code a_lo}.
+ * Combined they have (p-1) bits of significand but the sign bit of
{@code a_lo}
+ * contains a bit of information.
+ *
+ * @param a Value.
+ * @return the high part of the value.
+ * @see <a href="https://doi.org/10.1007/BF01397083">
+ * Dekker (1971) A floating-point technique for extending the available
precision</a>
+ */
+ static double splitHigh(double a) {
+ final double c = MULTIPLIER * a;
+ return c - (c - a);
+ }
+
+ /**
+ * Compute the round-off from the square of a split number with {@code
low} and {@code high}
+ * components. Uses Dekker's algorithm for split multiplication modified
for a square product.
+ *
+ * <p>Note: This is candidate to be replaced with {@code Math.fma(x, x, -x
* x)} to compute
+ * the round-off from the square product {@code x * x}. This would remove
the requirement
+ * to compute the split number and make this method redundant. {@code
Math.fma} requires
+ * JDK 9 and FMA hardware support.
+ *
+ * @param low Low part of number.
+ * @param high High part of number.
+ * @param square Square of the number.
+ * @return <code>low * low - (((product - high * high) - low * high) -
high * low)</code>
+ * @see <a
href="http://www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps">
+ * Shewchuk (1997) Theorum 18</a>
+ */
+ static double squareLow(double low, double high, double square) {
Review Comment:
Should be private. Or add a TODO to make private.
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java:
##########
@@ -300,6 +300,107 @@ private static void assertConjugateEquality(Complex z,
}
}
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal. This includes the sign of zero.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * <h2>ISO C99 equalities</h2>
+ *
+ * <p>Note that this method currently enforces the conjugate equalities
for some cases
+ * where the sign of the real/imaginary parts are unspecified in ISO C99.
This is
+ * allowed (since they are unspecified). The sign specification is
appropriately
+ * handled during testing of odd/even functions. There are some functions
where it
+ * is not possible to satisfy the conjugate equality and also the odd/even
rule.
+ * The compromise made here is to satisfy only one and the other is
allowed to fail
+ * only on the sign of the output result. Known functions where this
applies are:
+ *
+ * <ul>
+ * <li>asinh(NaN, inf)
+ * <li>atanh(NaN, inf)
+ * <li>cosh(NaN, 0.0)
+ * <li>sinh(inf, inf)
+ * <li>sinh(inf, nan)
+ * </ul>
+ *
+ * @param operation the operation
+ */
+ private static void assertConjugateEquality(UnaryOperator<Complex>
operation,
+
ComplexUnaryOperator<ComplexNumber> operation2) {
+ // Edge cases. Inf/NaN are specifically handled in the C99 test cases
+ // but are repeated here to enforce the conjugate equality even when
the C99
+ // standard does not specify a sign. This may be revised in the future.
+ final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1,
+ Double.POSITIVE_INFINITY, Double.NaN};
+ for (final double x : parts) {
+ for (final double y : parts) {
+ // No conjugate for imaginary NaN
+ if (!Double.isNaN(y)) {
+ assertConjugateEquality(complex(x, y), operation,
operation2, UnspecifiedSign.NONE);
+ }
+ }
+ }
+ // Random numbers
+ final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create();
+ for (int i = 0; i < 100; i++) {
+ final double x = next(rng);
+ final double y = next(rng);
+ assertConjugateEquality(complex(x, y), operation, operation2,
UnspecifiedSign.NONE);
+ }
+ }
+
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal; the sign of the complex number is
first processed
+ * using the provided sign specification.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param z the complex number
+ * @param operation1 the operation on the Complex object
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param sign the sign specification
+ */
+ private static void assertConjugateEquality(Complex z,
+ UnaryOperator<Complex> operation1,
+ ComplexUnaryOperator<ComplexNumber> operation2,
+ UnspecifiedSign sign) {
+
+ final Complex zConj = z.conj();
+ final Complex c1 = operation1.apply(zConj);
+ final Complex c2 = operation1.apply(z).conj();
+
+ final ComplexNumber cn1 = operation2.apply(zConj.getReal(),
zConj.getImaginary(), ComplexNumber::new);
+ final ComplexNumber cn2 = operation2.apply(z.getReal(),
z.getImaginary(), ComplexNumber::conj);
+
+ final Complex t1 = sign.removeSign(c1);
+ final Complex t2 = sign.removeSign(c2);
+
+ // Test for binary equality
+ if (!equals(t1.getReal(), t2.getReal()) ||
+ !equals(t1.getImaginary(), t2.getImaginary())) {
+ Assertions.fail(
+ String.format("Conjugate equality failed (z=%s). Expected: %s
but was: %s (Unspecified sign = %s)",
+ z, c1, c2, sign));
+ }
+
+ TestUtils.assertSame(c1, cn1);
Review Comment:
Redundant ...
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java:
##########
@@ -300,6 +300,107 @@ private static void assertConjugateEquality(Complex z,
}
}
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal. This includes the sign of zero.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * <h2>ISO C99 equalities</h2>
+ *
+ * <p>Note that this method currently enforces the conjugate equalities
for some cases
+ * where the sign of the real/imaginary parts are unspecified in ISO C99.
This is
+ * allowed (since they are unspecified). The sign specification is
appropriately
+ * handled during testing of odd/even functions. There are some functions
where it
+ * is not possible to satisfy the conjugate equality and also the odd/even
rule.
+ * The compromise made here is to satisfy only one and the other is
allowed to fail
+ * only on the sign of the output result. Known functions where this
applies are:
+ *
+ * <ul>
+ * <li>asinh(NaN, inf)
+ * <li>atanh(NaN, inf)
+ * <li>cosh(NaN, 0.0)
+ * <li>sinh(inf, inf)
+ * <li>sinh(inf, nan)
+ * </ul>
+ *
+ * @param operation the operation
+ */
+ private static void assertConjugateEquality(UnaryOperator<Complex>
operation,
+
ComplexUnaryOperator<ComplexNumber> operation2) {
+ // Edge cases. Inf/NaN are specifically handled in the C99 test cases
+ // but are repeated here to enforce the conjugate equality even when
the C99
+ // standard does not specify a sign. This may be revised in the future.
+ final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1,
+ Double.POSITIVE_INFINITY, Double.NaN};
+ for (final double x : parts) {
+ for (final double y : parts) {
+ // No conjugate for imaginary NaN
+ if (!Double.isNaN(y)) {
+ assertConjugateEquality(complex(x, y), operation,
operation2, UnspecifiedSign.NONE);
+ }
+ }
+ }
+ // Random numbers
+ final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create();
+ for (int i = 0; i < 100; i++) {
+ final double x = next(rng);
+ final double y = next(rng);
+ assertConjugateEquality(complex(x, y), operation, operation2,
UnspecifiedSign.NONE);
+ }
+ }
+
+ /**
+ * Assert the operation on the complex number satisfies the conjugate
equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal; the sign of the complex number is
first processed
+ * using the provided sign specification.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
Review Comment:
Missing `<p>`
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java:
##########
@@ -508,6 +609,109 @@ private static void assertComplex(Complex z,
}
}
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ * If the imaginary part is not NaN the operation must also satisfy the
conjugate equality.
+ *
+ * <pre>
+ * op(conj(z)) = conj(op(z))
+ * </pre>
+ *
+ * <p>The results must be binary equal. This includes the sign of zero.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
Review Comment:
New paragraph requires `<p>` tag
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexNumber.java:
##########
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.complex;
+
+/**
+ * Cartesian representation of a complex number. The complex number is
expressed
+ * in the form \( a + ib \) where \( a \) and \( b \) are real numbers and \(
i \)
+ * is the imaginary unit which satisfies the equation \( i^2 = -1 \). For the
+ * complex number \( a + ib \), \( a \) is called the <em>real part</em> and
+ * \( b \) is called the <em>imaginary part</em>.
+ */
+class ComplexNumber {
+
+ /** The real part. */
+ private final double real;
+ /** The imaginary part. */
+ private final double imaginary;
+
+ /**
+ * Constructor representing a complex number by its real and imaginary
parts.
+ * Takes in real and imaginary and sets it to this complex number's real
and imaginary
Review Comment:
Second sentence is not required. I think it may be left over from when this
was a method not a constructor.
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java:
##########
@@ -81,6 +81,57 @@ private static void assertComplex(double a, double b,
CReferenceTest.assertComplex(c, name, operation, e, maxUlps);
}
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal if there are no floating-point
values between them.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param a Real part.
+ * @param b Imaginary part.
+ * @param name The operation name.
+ * @param operation1 the operation on the Complex object.
+ * @param operation2 the operation on the complex real and imaginary parts
+ * @param x Expected real part.
+ * @param y Expected imaginary part.
+ */
+ private static void assertComplex(double a, double b,
+ String name, UnaryOperator<Complex>
operation1,
+ ComplexUnaryOperator<ComplexNumber>
operation2,
+ double x, double y) {
+ assertComplex(a, b, name, operation1, operation2, x, y, 1);
+ }
+
+ /**
+ * Assert the operation on the complex number is equal to the expected
value.
+ *
+ * <p>The results are considered equal within the provided units of least
+ * precision. The maximum count of numbers allowed between the two values
is
+ * {@code maxUlps - 1}.
+ *
+ * Assert the operation on the complex number is <em>exactly</em> equal to
the operation on
+ * complex real and imaginary parts.
+ *
+ * @param a Real part.
+ * @param b Imaginary part.
+ * @param name The operation name.
+ * @param operation1 the operation on the Complex object.
Review Comment:
Drop `the`, capitalize, etc
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java:
##########
@@ -67,6 +68,19 @@ public static void assertSame(Complex expected, Complex
actual) {
Assertions.assertEquals(expected.getImaginary(),
actual.getImaginary());
}
+ /**
+ * Verifies that real and imaginary parts of the Complex and ComplexNumber
arguments are
+ * exactly the same as defined by {@link Double#compare(double, double)}.
Also
+ * ensures that NaN / infinite components match.
+ *
+ * @param expected the expected value
+ * @param actual the actual value
+ */
+ public static void assertSame(Complex expected, ComplexNumber actual) {
Review Comment:
Do we need this method? If so then add a message to the assertions for
"real" and "imaginary"
##########
commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexNumber.java:
##########
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.numbers.complex;
+
+/**
+ * Cartesian representation of a complex number. The complex number is
expressed
+ * in the form \( a + ib \) where \( a \) and \( b \) are real numbers and \(
i \)
+ * is the imaginary unit which satisfies the equation \( i^2 = -1 \). For the
+ * complex number \( a + ib \), \( a \) is called the <em>real part</em> and
+ * \( b \) is called the <em>imaginary part</em>.
+ */
+class ComplexNumber {
+
+ /** The real part. */
+ private final double real;
+ /** The imaginary part. */
+ private final double imaginary;
+
+ /**
+ * Constructor representing a complex number by its real and imaginary
parts.
+ * Takes in real and imaginary and sets it to this complex number's real
and imaginary
+ *
+ * @param real Real part \( a \) of the complex number \( (a +ib \).
+ * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib
\).
+ *
Review Comment:
Extra line
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]