/*
 * 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="https://www.open-std.org/JTC1/SC22/WG14/www/standards">
 *    ISO/IEC 9899 - Programming languages - C</a>
 */
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.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, 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.SPLIT_MIX_64.create();
        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, "/", NAN::equals, "(NaN,NaN)");
            }
            for (final Complex z : infinites) {
                assertOperation(z, w, Complex::divide, "/", NAN::equals, "(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.SPLIT_MIX_64.create();
        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.XO_RO_SHI_RO_128_PP.create();
        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="https://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="https://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);
    }
}
