blob: 09f84895fc7598482a32cce1795c508987b4d445 [file] [log] [blame]
/*
* 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;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.UnaryOperator;
/**
* Tests the standards defined by the C.99 standard for complex numbers
* defined in ISO/IEC 9899, Annex G.
*
* @see <a href="http://www.open-std.org/JTC1/SC22/WG14/www/standards">
* ISO/IEC 9899 - Programming languages - C</a>
*/
public class CStandardTest {
private static final double inf = Double.POSITIVE_INFINITY;
private static final double negInf = Double.NEGATIVE_INFINITY;
private static final double nan = Double.NaN;
private static final double max = Double.MAX_VALUE;
private static final double piOverFour = Math.PI / 4.0;
private static final double piOverTwo = Math.PI / 2.0;
private static final double threePiOverFour = 3.0 * Math.PI / 4.0;
private static final Complex oneZero = complex(1, 0);
private static final Complex zeroInf = complex(0, inf);
private static final Complex zeroNegInf = complex(0, negInf);
private static final Complex zeroNaN = complex(0, nan);
private static final Complex zeroPiTwo = complex(0.0, piOverTwo);
private static final Complex negZeroZero = complex(-0.0, 0);
private static final Complex negZeroNaN = complex(-0.0, nan);
private static final Complex infZero = complex(inf, 0);
private static final Complex infNaN = complex(inf, nan);
private static final Complex infInf = complex(inf, inf);
private static final Complex infPiTwo = complex(inf, piOverTwo);
private static final Complex infThreePiFour = complex(inf, threePiOverFour);
private static final Complex infPiFour = complex(inf, piOverFour);
private static final Complex infPi = complex(inf, Math.PI);
private static final Complex negInfInf = complex(negInf, inf);
private static final Complex negInfZero = complex(negInf, 0);
private static final Complex negInfNaN = complex(negInf, nan);
private static final Complex negInfPi = complex(negInf, Math.PI);
private static final Complex nanInf = complex(nan, inf);
private static final Complex nanNegInf = complex(nan, negInf);
private static final Complex nanZero = complex(nan, 0);
private static final Complex nanPiTwo = complex(nan, piOverTwo);
private static final Complex piTwoNaN = complex(piOverTwo, nan);
private static final Complex piNegInf = complex(Math.PI, negInf);
private static final Complex piTwoNegInf = complex(piOverTwo, negInf);
private static final Complex piTwoNegZero = complex(piOverTwo, -0.0);
private static final Complex threePiFourNegInf = complex(threePiOverFour, negInf);
private static final Complex piFourNegInf = complex(piOverFour, negInf);
private static final Complex NAN = complex(nan, nan);
private static final Complex maxMax = complex(max, max);
private static final Complex maxNan = complex(max, nan);
private static final Complex nanMax = complex(nan, max);
/** Finite numbers (positive and negative with zero). */
private static final double[] finite;
/** Positive finite numbers (with zero). */
private static final double[] positiveFinite;
/** Non-zero finite numbers (positive and negative). */
private static final double[] nonZeroFinite;
/** Non-zero positive finite numbers. */
private static final double[] nonZeroPositiveFinite;
/** Non-zero finite and infinite numbers (positive and negative). */
private static final double[] nonZero;
static {
// Choose a range that covers 2 * Math.PI.
// This is important for the functions that use cis(y).
final int size = 13;
finite = new double[size * 2];
positiveFinite = new double[size];
for (int i = 0; i < size; i++) {
final double v = i * 0.5;
finite[i * 2] = -v;
finite[i * 2 + 1] = v;
positiveFinite[i] = v;
}
// Copy without zero
nonZeroFinite = Arrays.copyOfRange(finite, 2, finite.length);
nonZeroPositiveFinite = Arrays.copyOfRange(positiveFinite, 1, positiveFinite.length);
nonZero = Arrays.copyOf(nonZeroFinite, nonZeroFinite.length + 2);
nonZero[nonZeroFinite.length] = Double.POSITIVE_INFINITY;
nonZero[nonZeroFinite.length + 1] = Double.NEGATIVE_INFINITY;
// Arrange numerically
Arrays.sort(finite);
Arrays.sort(nonZeroFinite);
Arrays.sort(nonZero);
}
/**
* The function type (e.g. odd or even).
*/
private enum FunctionType {
/** Odd: f(z) = -f(-z). */
ODD,
/** Even: f(z) = f(-z). */
EVEN,
/** Not Odd or Even. */
NONE;
}
/**
* An enum containing functionality to remove the sign from complex parts.
*/
private enum UnspecifiedSign {
/** Remove the sign from the real component. */
REAL {
@Override
Complex removeSign(Complex c) {
return negative(c.getReal()) ? complex(-c.getReal(), c.getImaginary()) : c;
}
},
/** Remove the sign from the imaginary component. */
IMAGINARY {
@Override
Complex removeSign(Complex c) {
return negative(c.getImaginary()) ? complex(c.getReal(), -c.getImaginary()) : c;
}
},
/** Remove the sign from the real and imaginary component. */
REAL_IMAGINARY {
@Override
Complex removeSign(Complex c) {
return IMAGINARY.removeSign(REAL.removeSign(c));
}
},
/** Do not remove the sign. */
NONE {
@Override
Complex removeSign(Complex c) {
return c;
}
};
/**
* Removes the sign from the complex real and or imaginary parts.
*
* @param c the complex
* @return the complex
*/
abstract Complex removeSign(Complex c);
/**
* Check that a value is negative. It must meet all the following conditions:
* <ul>
* <li>it is not {@code NaN},</li>
* <li>it is negative signed,</li>
* </ul>
*
* <p>Note: This is true for negative zero.</p>
*
* @param d Value.
* @return {@code true} if {@code d} is negative.
*/
private static boolean negative(double d) {
return d < 0 || Double.doubleToLongBits(d) == Double.doubleToLongBits(-0.0);
}
}
/**
* Assert the two complex numbers have equivalent real and imaginary components as
* defined by the {@code ==} operator.
*
* @param c1 the first complex (actual)
* @param c2 the second complex (expected)
*/
private static void assertComplex(Complex c1, Complex c2) {
// Use a delta of zero to allow comparison of -0.0 to 0.0
Assertions.assertEquals(c2.getReal(), c1.getReal(), 0.0, "real");
Assertions.assertEquals(c2.getImaginary(), c1.getImaginary(), 0.0, "imaginary");
}
/**
* Assert the operation on the two complex numbers.
*
* @param c1 the first complex
* @param c2 the second complex
* @param operation the operation
* @param operationName the operation name
* @param expected the expected
* @param expectedName the expected name
*/
private static void assertOperation(Complex c1, Complex c2,
BiFunction<Complex, Complex, Complex> operation, String operationName,
Predicate<Complex> expected, String expectedName) {
final Complex z = operation.apply(c1, c2);
Assertions.assertTrue(expected.test(z),
() -> String.format("%s expected: %s %s %s = %s", expectedName, c1, operationName, c2, 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.
*
* <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) {
// 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, UnspecifiedSign.NONE);
}
}
}
// Random numbers
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 100; i++) {
final double x = next(rng);
final double y = next(rng);
assertConjugateEquality(complex(x, y), operation, 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.
*
* @param z the complex number
* @param operation the operation
* @param sign the sign specification
*/
private static void assertConjugateEquality(Complex z,
UnaryOperator<Complex> operation, UnspecifiedSign sign) {
final Complex c1 = operation.apply(z.conj());
final Complex c2 = operation.apply(z).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));
}
}
/**
* Assert the operation on the complex number is odd or even.
*
* <pre>
* Odd : f(z) = -f(-z)
* Even: f(z) = f(-z)
* </pre>
*
* <p>The results must be binary equal. This includes the sign of zero.
*
* @param operation the operation
* @param type the type
*/
private static void assertFunctionType(UnaryOperator<Complex> operation, FunctionType type) {
// Note: It may not be possible to satisfy the conjugate equality
// and be an odd/even function with regard to zero.
// The C99 standard allows for these cases to have unspecified sign.
// This test ignores parts that can result in unspecified signed results.
// The valid edge cases should be tested for each function separately.
if (type == FunctionType.NONE) {
return;
}
// Edge cases around zero.
final double[] parts = {-2, -1, -0.0, 0.0, 1, 2};
for (final double x : parts) {
for (final double y : parts) {
assertFunctionType(complex(x, y), operation, type, UnspecifiedSign.NONE);
}
}
// Random numbers
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 100; i++) {
final double x = next(rng);
final double y = next(rng);
assertFunctionType(complex(x, y), operation, type, UnspecifiedSign.NONE);
}
}
/**
* Assert the operation on the complex number is odd or even.
*
* <pre>
* Odd : f(z) = -f(-z)
* Even: f(z) = f(-z)
* </pre>
*
* <p>The results must be binary equal; the sign of the complex number is first processed
* using the provided sign specification.
*
* @param z the complex number
* @param operation the operation
* @param type the type (assumed to be ODD/EVEN)
* @param sign the sign specification
*/
private static void assertFunctionType(Complex z,
UnaryOperator<Complex> operation, FunctionType type, UnspecifiedSign sign) {
final Complex c1 = operation.apply(z);
Complex c2 = operation.apply(z.negate());
if (type == FunctionType.ODD) {
c2 = c2.negate();
}
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("%s equality failed (z=%s, -z=%s). Expected: %s but was: %s (Unspecified sign = %s)",
type, z, z.negate(), c1, c2, sign));
new Exception().printStackTrace();
}
}
/**
* 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.
*
* @param z the complex
* @param operation the operation
* @param expected the expected
*/
private static void assertComplex(Complex z,
UnaryOperator<Complex> operation, Complex expected) {
assertComplex(z, operation, expected, FunctionType.NONE, UnspecifiedSign.NONE);
}
/**
* 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.
*
* @param z the complex
* @param operation the operation
* @param expected the expected
* @param sign the sign specification
*/
private static void assertComplex(Complex z,
UnaryOperator<Complex> operation, Complex expected, UnspecifiedSign sign) {
assertComplex(z, operation, expected, FunctionType.NONE, sign);
}
/**
* 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>If the function type is ODD/EVEN the operation must satisfy the function type equality.
*
* <pre>
* Odd : f(z) = -f(-z)
* Even: f(z) = f(-z)
* </pre>
*
* <p>The results must be binary equal. This includes the sign of zero.
*
* @param z the complex
* @param operation the operation
* @param expected the expected
* @param type the type
*/
private static void assertComplex(Complex z,
UnaryOperator<Complex> operation, Complex expected,
FunctionType type) {
assertComplex(z, operation, expected, type, UnspecifiedSign.NONE);
}
/**
* 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>If the function type is ODD/EVEN the operation must satisfy the function type equality.
*
* <pre>
* Odd : f(z) = -f(-z)
* Even: f(z) = f(-z)
* </pre>
*
* <p>An ODD/EVEN function is also tested that the conjugate equalities hold with {@code -z}.
* This effectively enumerates testing: (re, im); (re, -im); (-re, -im); and (-re, im).
*
* <p>The results must be binary equal; the sign of the complex number is first processed
* using the provided sign specification.
*
* @param z the complex
* @param operation the operation
* @param expected the expected
* @param type the type
* @param sign the sign specification
*/
private static void assertComplex(Complex z,
UnaryOperator<Complex> operation, Complex expected,
FunctionType type, UnspecifiedSign sign) {
// Developer note: Set the sign specification to UnspecifiedSign.NONE
// to see which equalities fail. They should be for input defined
// in ISO C99 with an unspecified output sign, e.g.
// sign = UnspecifiedSign.NONE
// Test the operation
final Complex c = operation.apply(z);
final Complex t1 = sign.removeSign(c);
final Complex t2 = sign.removeSign(expected);
if (!equals(t1.getReal(), t2.getReal()) ||
!equals(t1.getImaginary(), t2.getImaginary())) {
Assertions.fail(
String.format("Operation failed (z=%s). Expected: %s but was: %s (Unspecified sign = %s)",
z, expected, c, sign));
}
if (!Double.isNaN(z.getImaginary())) {
assertConjugateEquality(z, operation, sign);
}
if (type != FunctionType.NONE) {
assertFunctionType(z, operation, type, sign);
// An odd/even function should satisfy the conjugate equality
// on the negated complex. This ensures testing the equalities
// hold for:
// (re, im) = (re, -im)
// (re, im) = (-re, -im) (even)
// = -(-re, -im) (odd)
// (-re, -im) = (-re, im)
if (!Double.isNaN(z.getImaginary())) {
assertConjugateEquality(z.negate(), operation, sign);
}
}
}
/**
* Assert {@link Complex#abs()} is functionally equivalent to using
* {@link Math#hypot(double, double)}. If the results differ the true result
* is computed with extended precision. The test fails if the result is further
* than the provided ULPs from the reference result.
*
* <p>This can be used to assert that the custom implementation of abs() is no worse than
* {@link Math#hypot(double, double)} which aims to be within 1 ULP of the exact result.
*
* <p>Note: This method will not handle an input complex that is infinite or nan so should
* not be used for edge case tests.
*
* <p>Note: The true result is the sum {@code x^2 + y^2} computed using BigDecimal,
* converted to a double and the sqrt computed using standard precision.
* This is not the exact result as the BigDecimal
* sqrt() function was added in Java 9 and is unavailable for the current build target.
* In this case we require a measure of how close you can get to the nearest-double
* representing the answer, and the not the exact distance from the answer, so this
* is valid assuming {@link Math#sqrt(double)} has no error. The test then becomes a
* measure of the accuracy of the high-precision sum {@code x^2 + y^2}.
*
* @param z the complex
* @param ulps the maximum allowed ULPs from the exact result
*/
private static void assertAbs(Complex z, int ulps) {
double x = z.getReal();
double y = z.getImaginary();
// For speed use Math.hypot as the reference, not BigDecimal computation.
final double expected = Math.hypot(x, y);
final double observed = z.abs();
if (expected == observed) {
// This condition will occur in the majority of cases.
return;
}
// Compute the 'exact' result.
// JDK 9 BigDecimal.sqrt() is not available so compute the standard sqrt of the
// high precision sum. Do scaling as the high precision sum may not be in the
// range of a double.
int scale = 0;
x = Math.abs(x);
y = Math.abs(y);
if (Math.max(x, y) > 0x1.0p+500) {
scale = Math.getExponent(Math.max(x, y));
} else if (Math.min(x, y) < 0x1.0p-500) {
scale = Math.getExponent(Math.min(x, y));
}
if (scale != 0) {
x = Math.scalb(x, -scale);
y = Math.scalb(y, -scale);
}
// Compute and re-scale. 'exact' must be effectively final for use in the
// assertion message supplier.
final double result = Math.sqrt(new BigDecimal(x).pow(2).add(new BigDecimal(y).pow(2)).doubleValue());
final double exact = scale != 0 ? Math.scalb(result, scale) : result;
if (exact == observed) {
// Different from Math.hypot but matches the 'exact' result
return;
}
// Distance from the 'exact' result should be within tolerance.
final long obsBits = Double.doubleToLongBits(observed);
final long exactBits = Double.doubleToLongBits(exact);
final long obsUlp = Math.abs(exactBits - obsBits);
Assertions.assertTrue(obsUlp <= ulps, () -> {
// Compute for Math.hypot for reference.
final long expBits = Double.doubleToLongBits(expected);
final long expUlp = Math.abs(exactBits - expBits);
return String.format("%s.abs(). Expected %s, was %s (%d ulps). hypot %s (%d ulps)",
z, exact, observed, obsUlp, expected, expUlp);
});
}
/**
* Assert {@link Complex#abs()} functions as per {@link Math#hypot(double, double)}.
* The two numbers for {@code z = x + iy} are generated from the two function.
*
* <p>The functions should not generate numbers that are infinite or nan.
*
* @param rng Source of randomness
* @param fx Function to generate x
* @param fy Function to generate y
* @param samples Number of samples
*/
private static void assertAbs(UniformRandomProvider rng,
ToDoubleFunction<UniformRandomProvider> fx,
ToDoubleFunction<UniformRandomProvider> fy,
int samples) {
for (int i = 0; i < samples; i++) {
double x = fx.applyAsDouble(rng);
double y = fy.applyAsDouble(rng);
assertAbs(Complex.ofCartesian(x, y), 1);
}
}
/**
* Creates a sub-normal number with up to 52-bits in the mantissa. The number of bits
* to drop must be in the range [0, 51].
*
* @param rng Source of randomness
* @param drop The number of mantissa bits to drop.
* @return the number
*/
private static double createSubNormalNumber(UniformRandomProvider rng, int drop) {
return Double.longBitsToDouble(rng.nextLong() >>> (12 + drop));
}
/**
* Creates a number in the range {@code [1, 2)} with up to 52-bits in the mantissa.
* Then modifies the exponent by the given amount.
*
* @param rng Source of randomness
* @param exponent Amount to change the exponent (in range [-1023, 1023])
* @return the number
*/
private static double createFixedExponentNumber(UniformRandomProvider rng, int exponent) {
return Double.longBitsToDouble((rng.nextLong() >>> 12) | ((1023L + exponent) << 52));
}
/**
* Returns {@code true} if the values are equal according to semantics of
* {@link Double#equals(Object)}.
*
* @param x Value
* @param y Value
* @return {@code Double.valueof(x).equals(Double.valueOf(y))}
*/
private static boolean equals(double x, double y) {
return Double.doubleToLongBits(x) == Double.doubleToLongBits(y);
}
/**
* Utility to create a Complex.
*
* @param real the real
* @param imaginary the imaginary
* @return the complex
*/
private static Complex complex(double real, double imaginary) {
return Complex.ofCartesian(real, imaginary);
}
/**
* Creates a list of Complex infinites.
*
* @return the list
*/
private static ArrayList<Complex> createInfinites() {
final double[] values = {0, 1, inf, negInf, nan};
return createCombinations(values, Complex::isInfinite);
}
/**
* Creates a list of Complex finites that are not zero.
*
* @return the list
*/
private static ArrayList<Complex> createNonZeroFinites() {
final double[] values = {-1, -0, 0, 1, Double.MAX_VALUE};
return createCombinations(values, c -> !CStandardTest.isZero(c));
}
/**
* Creates a list of Complex finites that are zero: [0,0], [-0,0], [0,-0], [-0,-0].
*
* @return the list
*/
private static ArrayList<Complex> createZeroFinites() {
final double[] values = {-0, 0};
return createCombinations(values, c -> true);
}
/**
* Creates a list of Complex NaNs.
*
* @return the list
*/
private static ArrayList<Complex> createNaNs() {
final double[] values = {0, 1, nan};
return createCombinations(values, Complex::isNaN);
}
/**
* Creates a list of Complex numbers as an all-vs-all combinations that pass the
* condition.
*
* @param values the values
* @param condition the condition
* @return the list
*/
private static ArrayList<Complex> createCombinations(final double[] values, Predicate<Complex> condition) {
final ArrayList<Complex> list = new ArrayList<>();
for (final double re : values) {
for (final double im : values) {
final Complex z = complex(re, im);
if (condition.test(z)) {
list.add(z);
}
}
}
return list;
}
/**
* Checks if the complex is zero. This method uses the {@code ==} operator and allows
* equality between signed zeros: {@code -0.0 == 0.0}.
*
* @param c the complex
* @return true if zero
*/
private static boolean isZero(Complex c) {
return c.getReal() == 0 && c.getImaginary() == 0;
}
/**
* ISO C Standard G.5 (4).
*/
@Test
void testMultiply() {
final ArrayList<Complex> infinites = createInfinites();
final ArrayList<Complex> nonZeroFinites = createNonZeroFinites();
final ArrayList<Complex> zeroFinites = createZeroFinites();
// C.99 refers to non-zero finites.
// Standard multiplication of zero with infinites is not defined.
Assertions.assertEquals(nan, 0.0 * inf, "0 * inf");
Assertions.assertEquals(nan, 0.0 * negInf, "0 * -inf");
Assertions.assertEquals(nan, -0.0 * inf, "-0 * inf");
Assertions.assertEquals(nan, -0.0 * negInf, "-0 * -inf");
// "if one operand is an infinity and the other operand is a nonzero finite number or an
// infinity, then the result of the * operator is an infinity;"
for (final Complex z : infinites) {
for (final Complex w : infinites) {
assertOperation(z, w, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(w, z, Complex::multiply, "*", Complex::isInfinite, "Inf");
}
for (final Complex w : nonZeroFinites) {
assertOperation(z, w, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(w, z, Complex::multiply, "*", Complex::isInfinite, "Inf");
}
// C.99 refers to non-zero finites.
// Infer that Complex multiplication of zero with infinites is not defined.
for (final Complex w : zeroFinites) {
assertOperation(z, w, Complex::multiply, "*", Complex::isNaN, "NaN");
assertOperation(w, z, Complex::multiply, "*", Complex::isNaN, "NaN");
}
}
// ISO C Standard in Annex G is missing an explicit definition of how to handle NaNs.
// We will assume multiplication by (nan,nan) is not allowed.
// It is undefined how to multiply when a complex has only one NaN component.
// The reference implementation in Annex G allows it.
// The GNU g++ compiler computes:
// (1e300 + i 1e300) * (1e30 + i NAN) = inf + i inf
// Thus this is allowing some computations with NaN.
// Check multiply with (NaN,NaN) is not corrected
final double[] values = {0, 1, inf, negInf, nan};
for (final Complex z : createCombinations(values, c -> true)) {
assertOperation(z, NAN, Complex::multiply, "*", Complex::isNaN, "NaN");
assertOperation(NAN, z, Complex::multiply, "*", Complex::isNaN, "NaN");
}
// Test multiply cases which result in overflow are corrected to infinity
assertOperation(maxMax, maxMax, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(maxNan, maxNan, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(nanMax, maxNan, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(maxNan, nanMax, Complex::multiply, "*", Complex::isInfinite, "Inf");
assertOperation(nanMax, nanMax, Complex::multiply, "*", Complex::isInfinite, "Inf");
}
/**
* ISO C Standard G.5 (4).
*/
@Test
void testDivide() {
final ArrayList<Complex> infinites = createInfinites();
final ArrayList<Complex> nonZeroFinites = createNonZeroFinites();
final ArrayList<Complex> zeroFinites = createZeroFinites();
final ArrayList<Complex> nans = createNaNs();
final ArrayList<Complex> finites = new ArrayList<>(nonZeroFinites);
finites.addAll(zeroFinites);
// "if the first operand is an infinity and the second operand is a finite number, then the
// result of the / operator is an infinity;"
for (final Complex z : infinites) {
for (final Complex w : nonZeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
for (final Complex w : zeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
// Check inf/inf cannot be done
for (final Complex w : infinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isNaN, "NaN");
}
}
// "if the first operand is a finite number and the second operand is an infinity, then the
// result of the / operator is a zero;"
for (final Complex z : finites) {
for (final Complex w : infinites) {
assertOperation(z, w, Complex::divide, "/", CStandardTest::isZero, "Zero");
}
}
// "if the first operand is a nonzero finite number or an infinity and the second operand is
// a zero, then the result of the / operator is an infinity."
for (final Complex w : zeroFinites) {
for (final Complex z : nonZeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
for (final Complex z : infinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
}
// ISO C Standard in Annex G is missing an explicit definition of how to handle NaNs.
// The reference implementation does not correct for divide by NaN components unless
// infinite.
for (final Complex w : nans) {
for (final Complex z : finites) {
assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)");
}
for (final Complex z : infinites) {
assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)");
}
}
// Check (NaN,NaN) divide is not corrected for the edge case of divide by zero or infinite
for (final Complex w : zeroFinites) {
assertOperation(NAN, w, Complex::divide, "/", Complex::isNaN, "NaN");
}
for (final Complex w : infinites) {
assertOperation(NAN, w, Complex::divide, "/", Complex::isNaN, "NaN");
}
}
/**
* ISO C Standard G.6 (3).
*/
@Test
void testSqrt1() {
assertComplex(complex(-2.0, 0.0).sqrt(), complex(0.0, Math.sqrt(2)));
assertComplex(complex(-2.0, -0.0).sqrt(), complex(0.0, -Math.sqrt(2)));
}
/**
* ISO C Standard G.6 (7).
*/
@Test
void testImplicitTrig() {
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 100; i++) {
final double re = next(rng);
final double im = next(rng);
final Complex z = complex(re, im);
final Complex iz = z.multiplyImaginary(1);
assertComplex(z.asin(), iz.asinh().multiplyImaginary(-1));
assertComplex(z.atan(), iz.atanh().multiplyImaginary(-1));
assertComplex(z.cos(), iz.cosh());
assertComplex(z.sin(), iz.sinh().multiplyImaginary(-1));
assertComplex(z.tan(), iz.tanh().multiplyImaginary(-1));
}
}
/**
* Create a number in the range {@code [-5,5)}.
*
* @param rng the random generator
* @return the number
*/
private static double next(UniformRandomProvider rng) {
// Note: [0, 1) minus 1 is [-1, 0). This occurs half the time to create [-1, 1).
return (rng.nextDouble() - rng.nextInt(1)) * 5;
}
/**
* ISO C Standard G.6 (6) for abs().
* Functionality is defined by ISO C Standard F.9.4.3 hypot function.
*/
@Test
void testAbs() {
Assertions.assertEquals(inf, complex(inf, nan).abs());
Assertions.assertEquals(inf, complex(negInf, nan).abs());
final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);
for (int i = 0; i < 10; i++) {
final double x = next(rng);
final double y = next(rng);
Assertions.assertEquals(complex(x, y).abs(), complex(y, x).abs());
Assertions.assertEquals(complex(x, y).abs(), complex(x, -y).abs());
Assertions.assertEquals(Math.abs(x), complex(x, 0.0).abs());
Assertions.assertEquals(Math.abs(x), complex(x, -0.0).abs());
Assertions.assertEquals(inf, complex(inf, y).abs());
Assertions.assertEquals(inf, complex(negInf, y).abs());
}
// Test verses Math.hypot due to the use of a custom implementation.
// First test edge cases. Use negatives to test the sign is correctly removed.
final double[] parts = {-0.0, -Double.MIN_VALUE, -Double.MIN_NORMAL, -Double.MAX_VALUE,
Double.NEGATIVE_INFINITY, Double.NaN};
for (final double x : parts) {
for (final double y : parts) {
Assertions.assertEquals(Math.hypot(x, y), complex(x, y).abs());
}
}
// The reference fdlibm hypot implementation orders using the upper 32-bits of the double.
// Tests using random numbers that differ in only the lower 32-bits
// show a frequency of <1e-9 that the computation is not commutative: f(x, y) != f(y, x)
// Test known cases where ordering is required on the lower 32-bits to ensure |z| == |iz|.
// These cases would fail a direct fdlibm conversion of this:
// https://www.netlib.org/fdlibm/e_hypot.c
for (final double[] pair : new double[][] {
{1.3122561682406755, 1.3122565442732959},
{1.40905821964671, 1.4090583434236112},
{1.912164268932753, 1.9121638616231227}}) {
final Complex z = complex(pair[0], pair[1]);
Assertions.assertEquals(z.abs(), z.multiplyImaginary(1).abs(), "Expected |z| == |iz|");
}
// Test with a range of numbers.
// Sub-normals require special handling so we use different variations of these to
// ensure they are handled correctly.
// Note:
// 1 ULP differences with Math.hypot can be observed due to the different implementations.
// For normal numbers observable differences require billions of numbers to show a
// few hundred cases of lower ULPs and a magnitude smaller count of higher ULPs.
// For sub-normal numbers a few thousand examples can demonstrate
// a larger count of 1 ULP improvements than 1 ULP errors verses Math.hypot.
// A formal statistical test to demonstrate differences are significant is not implemented.
// This test simply asserts the answer is either the same as Math.hypot or else is within
// 1 ULP of a high precision computation.
final int samples = 100;
assertAbs(rng, r -> createSubNormalNumber(r, 0), r -> createSubNormalNumber(r, 0), samples);
assertAbs(rng, r -> createSubNormalNumber(r, 0), r -> createSubNormalNumber(r, 1), samples);
assertAbs(rng, r -> createSubNormalNumber(r, 0), r -> createSubNormalNumber(r, 2), samples);
// Numbers on the same scale (fixed exponent)
assertAbs(rng, r -> createFixedExponentNumber(r, 0), r -> createFixedExponentNumber(r, 0), samples);
// Numbers on different scales
assertAbs(rng, r -> createFixedExponentNumber(r, 0), r -> createFixedExponentNumber(r, 1), samples);
assertAbs(rng, r -> createFixedExponentNumber(r, 0), r -> createFixedExponentNumber(r, 2 + r.nextInt(10)), samples);
// Intermediate overflow / underflow
assertAbs(rng, r -> createFixedExponentNumber(r, 1022), r -> createFixedExponentNumber(r, 1022), samples);
assertAbs(rng, r -> createFixedExponentNumber(r, -1022), r -> createFixedExponentNumber(r, -1022), samples);
// Complex cis numbers
final ToDoubleFunction<UniformRandomProvider> cisGenerator = new ToDoubleFunction<UniformRandomProvider>() {
private double tmp = Double.NaN;
@Override
public double applyAsDouble(UniformRandomProvider rng) {
if (Double.isNaN(tmp)) {
double u = rng.nextDouble() * Math.PI;
tmp = Math.cos(u);
return Math.sin(u);
}
final double r = tmp;
tmp = Double.NaN;
return r;
}
};
assertAbs(rng, cisGenerator, cisGenerator, samples);
}
/**
* ISO C Standard G.6.1.1.
*/
@Test
void testAcos() {
final UnaryOperator<Complex> operation = Complex::acos;
assertConjugateEquality(operation);
assertComplex(Complex.ZERO, operation, piTwoNegZero);
assertComplex(negZeroZero, operation, piTwoNegZero);
assertComplex(zeroNaN, operation, piTwoNaN);
assertComplex(negZeroNaN, operation, piTwoNaN);
for (double x : finite) {
assertComplex(complex(x, inf), operation, piTwoNegInf);
}
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN);
}
for (double y : positiveFinite) {
assertComplex(complex(-inf, y), operation, piNegInf);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, zeroNegInf);
}
assertComplex(negInfInf, operation, threePiFourNegInf);
assertComplex(infInf, operation, piFourNegInf);
assertComplex(infNaN, operation, nanInf, UnspecifiedSign.IMAGINARY);
assertComplex(negInfNaN, operation, nanNegInf, UnspecifiedSign.IMAGINARY);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(nanInf, operation, nanNegInf);
assertComplex(NAN, operation, NAN);
}
/**
* ISO C Standard G.6.2.1.
*
* @see <a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1892.htm#dr_471">
* Complex math functions cacosh and ctanh</a>
*/
@Test
void testAcosh() {
final UnaryOperator<Complex> operation = Complex::acosh;
assertConjugateEquality(operation);
assertComplex(Complex.ZERO, operation, zeroPiTwo);
assertComplex(negZeroZero, operation, zeroPiTwo);
for (double x : finite) {
assertComplex(complex(x, inf), operation, infPiTwo);
}
assertComplex(zeroNaN, operation, nanPiTwo);
assertComplex(negZeroNaN, operation, nanPiTwo);
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN);
}
for (double y : positiveFinite) {
assertComplex(complex(-inf, y), operation, infPi);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, infZero);
}
assertComplex(negInfInf, operation, infThreePiFour);
assertComplex(infInf, operation, infPiFour);
assertComplex(infNaN, operation, infNaN);
assertComplex(negInfNaN, operation, infNaN);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(nanInf, operation, infNaN);
assertComplex(NAN, operation, NAN);
}
/**
* ISO C Standard G.6.2.2.
*/
@Test
void testAsinh() {
final UnaryOperator<Complex> operation = Complex::asinh;
final FunctionType type = FunctionType.ODD;
assertConjugateEquality(operation);
assertFunctionType(operation, type);
assertComplex(Complex.ZERO, operation, Complex.ZERO, type);
for (double x : positiveFinite) {
assertComplex(complex(x, inf), operation, infPiTwo, type);
}
for (double x : finite) {
assertComplex(complex(x, nan), operation, NAN, type);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, infZero, type);
}
assertComplex(infInf, operation, infPiFour, type);
assertComplex(infNaN, operation, infNaN, type);
assertComplex(nanZero, operation, nanZero, type);
for (double y : nonZeroFinite) {
assertComplex(complex(nan, y), operation, NAN, type);
}
assertComplex(nanInf, operation, infNaN, type, UnspecifiedSign.REAL);
assertComplex(NAN, operation, NAN, type);
}
/**
* ISO C Standard G.6.2.3.
*/
@Test
void testAtanh() {
final UnaryOperator<Complex> operation = Complex::atanh;
final FunctionType type = FunctionType.ODD;
assertConjugateEquality(operation);
assertFunctionType(operation, type);
assertComplex(Complex.ZERO, operation, Complex.ZERO, type);
assertComplex(zeroNaN, operation, zeroNaN, type);
assertComplex(oneZero, operation, infZero, type);
for (double x : positiveFinite) {
assertComplex(complex(x, inf), operation, zeroPiTwo, type);
}
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN, type);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, zeroPiTwo, type);
}
assertComplex(infInf, operation, zeroPiTwo, type);
assertComplex(infNaN, operation, zeroNaN, type);
assertComplex(nanZero, operation, NAN, type);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN, type);
}
assertComplex(nanInf, operation, zeroPiTwo, type, UnspecifiedSign.REAL);
assertComplex(NAN, operation, NAN, type);
}
/**
* ISO C Standard G.6.2.4.
*/
@Test
void testCosh() {
final UnaryOperator<Complex> operation = Complex::cosh;
final FunctionType type = FunctionType.EVEN;
assertConjugateEquality(operation);
assertFunctionType(operation, type);
assertComplex(Complex.ZERO, operation, Complex.ONE, type);
assertComplex(zeroInf, operation, nanZero, type, UnspecifiedSign.IMAGINARY);
assertComplex(zeroNaN, operation, nanZero, type, UnspecifiedSign.IMAGINARY);
for (double x : nonZeroFinite) {
assertComplex(complex(x, inf), operation, NAN, type);
}
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN, type);
}
assertComplex(infZero, operation, infZero, type);
for (double y : nonZeroFinite) {
assertComplex(complex(inf, y), operation, Complex.ofCis(y).multiply(inf), type);
}
assertComplex(infInf, operation, infNaN, type, UnspecifiedSign.REAL);
assertComplex(infNaN, operation, infNaN, type);
assertComplex(nanZero, operation, nanZero, type, UnspecifiedSign.IMAGINARY);
for (double y : nonZero) {
assertComplex(complex(nan, y), operation, NAN, type);
}
assertComplex(NAN, operation, NAN, type);
}
/**
* ISO C Standard G.6.2.5.
*/
@Test
void testSinh() {
final UnaryOperator<Complex> operation = Complex::sinh;
final FunctionType type = FunctionType.ODD;
assertConjugateEquality(operation);
assertFunctionType(operation, type);
assertComplex(Complex.ZERO, operation, Complex.ZERO, type);
assertComplex(zeroInf, operation, zeroNaN, type, UnspecifiedSign.REAL);
assertComplex(zeroNaN, operation, zeroNaN, type, UnspecifiedSign.REAL);
for (double x : nonZeroPositiveFinite) {
assertComplex(complex(x, inf), operation, NAN, type);
}
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN, type);
}
assertComplex(infZero, operation, infZero, type);
// Note: Error in the ISO C99 reference to use positive finite y but the zero case is different
for (double y : nonZeroFinite) {
assertComplex(complex(inf, y), operation, Complex.ofCis(y).multiply(inf), type);
}
assertComplex(infInf, operation, infNaN, type, UnspecifiedSign.REAL);
assertComplex(infNaN, operation, infNaN, type, UnspecifiedSign.REAL);
assertComplex(nanZero, operation, nanZero, type);
for (double y : nonZero) {
assertComplex(complex(nan, y), operation, NAN, type);
}
assertComplex(NAN, operation, NAN, type);
}
/**
* ISO C Standard G.6.2.6.
*
* @see <a href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1892.htm#dr_471">
* Complex math functions cacosh and ctanh</a>
*/
@Test
void testTanh() {
final UnaryOperator<Complex> operation = Complex::tanh;
final FunctionType type = FunctionType.ODD;
assertConjugateEquality(operation);
assertFunctionType(operation, type);
assertComplex(Complex.ZERO, operation, Complex.ZERO, type);
assertComplex(zeroInf, operation, zeroNaN, type);
for (double x : nonZeroFinite) {
assertComplex(complex(x, inf), operation, NAN, type);
}
assertComplex(zeroNaN, operation, zeroNaN, type);
for (double x : nonZeroFinite) {
assertComplex(complex(x, nan), operation, NAN, type);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, complex(1.0, Math.copySign(0, Math.sin(2 * y))), type);
}
assertComplex(infInf, operation, oneZero, type, UnspecifiedSign.IMAGINARY);
assertComplex(infNaN, operation, oneZero, type, UnspecifiedSign.IMAGINARY);
assertComplex(nanZero, operation, nanZero, type);
for (double y : nonZero) {
assertComplex(complex(nan, y), operation, NAN, type);
}
assertComplex(NAN, operation, NAN, type);
}
/**
* ISO C Standard G.6.3.1.
*/
@Test
void testExp() {
final UnaryOperator<Complex> operation = Complex::exp;
assertConjugateEquality(operation);
assertComplex(Complex.ZERO, operation, oneZero);
assertComplex(negZeroZero, operation, oneZero);
for (double x : finite) {
assertComplex(complex(x, inf), operation, NAN);
}
for (double x : finite) {
assertComplex(complex(x, nan), operation, NAN);
}
assertComplex(infZero, operation, infZero);
for (double y : finite) {
assertComplex(complex(-inf, y), operation, Complex.ofCis(y).multiply(0.0));
}
for (double y : nonZeroFinite) {
assertComplex(complex(inf, y), operation, Complex.ofCis(y).multiply(inf));
}
assertComplex(negInfInf, operation, Complex.ZERO, UnspecifiedSign.REAL_IMAGINARY);
assertComplex(infInf, operation, infNaN, UnspecifiedSign.REAL);
assertComplex(negInfNaN, operation, Complex.ZERO, UnspecifiedSign.REAL_IMAGINARY);
assertComplex(infNaN, operation, infNaN, UnspecifiedSign.REAL);
assertComplex(nanZero, operation, nanZero);
for (double y : nonZero) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(NAN, operation, NAN);
}
/**
* ISO C Standard G.6.3.2.
*/
@Test
void testLog() {
final UnaryOperator<Complex> operation = Complex::log;
assertConjugateEquality(operation);
assertComplex(negZeroZero, operation, negInfPi);
assertComplex(Complex.ZERO, operation, negInfZero);
for (double x : finite) {
assertComplex(complex(x, inf), operation, infPiTwo);
}
for (double x : finite) {
assertComplex(complex(x, nan), operation, NAN);
}
for (double y : positiveFinite) {
assertComplex(complex(-inf, y), operation, infPi);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, infZero);
}
assertComplex(negInfInf, operation, infThreePiFour);
assertComplex(infInf, operation, infPiFour);
assertComplex(negInfNaN, operation, infNaN);
assertComplex(infNaN, operation, infNaN);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(nanInf, operation, infNaN);
assertComplex(NAN, operation, NAN);
}
/**
* Same edge cases as log() since the real component is divided by Math.log(10) which
* has no effect on infinite or nan.
*/
@Test
void testLog10() {
final UnaryOperator<Complex> operation = Complex::log10;
assertConjugateEquality(operation);
assertComplex(negZeroZero, operation, negInfPi);
assertComplex(Complex.ZERO, operation, negInfZero);
for (double x : finite) {
assertComplex(complex(x, inf), operation, infPiTwo);
}
for (double x : finite) {
assertComplex(complex(x, nan), operation, NAN);
}
for (double y : positiveFinite) {
assertComplex(complex(-inf, y), operation, infPi);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, infZero);
}
assertComplex(negInfInf, operation, infThreePiFour);
assertComplex(infInf, operation, infPiFour);
assertComplex(negInfNaN, operation, infNaN);
assertComplex(infNaN, operation, infNaN);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(nanInf, operation, infNaN);
assertComplex(NAN, operation, NAN);
}
/**
* ISO C Standard G.6.4.2.
*/
@Test
void testSqrt() {
final UnaryOperator<Complex> operation = Complex::sqrt;
assertConjugateEquality(operation);
assertComplex(negZeroZero, operation, Complex.ZERO);
assertComplex(Complex.ZERO, operation, Complex.ZERO);
for (double x : finite) {
assertComplex(complex(x, inf), operation, infInf);
}
// Include infinity and nan for (x, inf).
assertComplex(infInf, operation, infInf);
assertComplex(negInfInf, operation, infInf);
assertComplex(nanInf, operation, infInf);
for (double x : finite) {
assertComplex(complex(x, nan), operation, NAN);
}
for (double y : positiveFinite) {
assertComplex(complex(-inf, y), operation, zeroInf);
}
for (double y : positiveFinite) {
assertComplex(complex(inf, y), operation, infZero);
}
assertComplex(negInfNaN, operation, nanInf, UnspecifiedSign.IMAGINARY);
assertComplex(infNaN, operation, infNaN);
for (double y : finite) {
assertComplex(complex(nan, y), operation, NAN);
}
assertComplex(NAN, operation, NAN);
}
}