This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch complex-gsoc-2022 in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
The following commit(s) were added to refs/heads/complex-gsoc-2022 by this push: new 7386b095 NUMBERS-188: Refactor complex binary functions to static methods 7386b095 is described below commit 7386b095d767ef184bed9bbbf6b2d84f46a617f4 Author: sumanth-rajkumar <53705316+sumanth-rajku...@users.noreply.github.com> AuthorDate: Tue Jul 26 15:12:54 2022 -0400 NUMBERS-188: Refactor complex binary functions to static methods Refactored binary methods accepting a Complex object as the second argument. --- .../apache/commons/numbers/complex/Complex.java | 304 +--------------- .../numbers/complex/ComplexBinaryOperator.java | 47 +++ .../commons/numbers/complex/ComplexFunctions.java | 389 ++++++++++++++++++++- .../commons/numbers/complex/CReferenceTest.java | 61 ++-- .../commons/numbers/complex/CStandardTest.java | 70 ++-- .../numbers/complex/ComplexEdgeCaseTest.java | 65 ++-- .../commons/numbers/complex/ComplexTest.java | 59 ++-- .../apache/commons/numbers/complex/TestUtils.java | 25 ++ 8 files changed, 595 insertions(+), 425 deletions(-) diff --git a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java index 4dc47117..9fc10012 100644 --- a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java +++ b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/Complex.java @@ -82,12 +82,6 @@ public final class Complex implements Serializable { /** A complex number representing {@code NaN + i NaN}. */ private static final Complex NAN = new Complex(Double.NaN, Double.NaN); - /** Exponent offset in IEEE754 representation. */ - private static final int EXPONENT_OFFSET = 1023; - /** Mask to remove the sign bit from a long. */ - private static final long UNSIGN_MASK = 0x7fff_ffff_ffff_ffffL; - /** Mask to extract the 52-bit mantissa from a long representation of a double. */ - private static final long MANTISSA_MASK = 0x000f_ffff_ffff_ffffL; /** Serializable version identifier. */ private static final long serialVersionUID = 20180201L; @@ -705,129 +699,7 @@ public final class Complex implements Serializable { * @see <a href="http://mathworld.wolfram.com/ComplexMultiplication.html">Complex Muliplication</a> */ public Complex multiply(Complex factor) { - return multiply(real, imaginary, factor.real, factor.imaginary); - } - - /** - * Returns a {@code Complex} whose value is: - * <pre> - * (a + i b)(c + i d) = (ac - bd) + i (ad + bc)</pre> - * - * <p>Recalculates to recover infinities as specified in C99 standard G.5.1. - * - * @param re1 Real component of first number. - * @param im1 Imaginary component of first number. - * @param re2 Real component of second number. - * @param im2 Imaginary component of second number. - * @return (a + b i)(c + d i). - */ - private static Complex multiply(double re1, double im1, double re2, double im2) { - double a = re1; - double b = im1; - double c = re2; - double d = im2; - final double ac = a * c; - final double bd = b * d; - final double ad = a * d; - final double bc = b * c; - double x = ac - bd; - double y = ad + bc; - - // -------------- - // NaN can occur if: - // - any of (a,b,c,d) are NaN (for NaN or Infinite complex numbers) - // - a multiplication of infinity by zero (ac,bd,ad,bc). - // - a subtraction of infinity from infinity (e.g. ac - bd) - // Note that (ac,bd,ad,bc) can be infinite due to overflow. - // - // Detect a NaN result and perform correction. - // - // Modification from the listing in ISO C99 G.5.1 (6) - // Do not correct infinity multiplied by zero. This is left as NaN. - // -------------- - - if (Double.isNaN(x) && Double.isNaN(y)) { - // Recover infinities that computed as NaN+iNaN ... - boolean recalc = false; - if ((Double.isInfinite(a) || Double.isInfinite(b)) && - isNotZero(c, d)) { - // This complex is infinite. - // "Box" the infinity and change NaNs in the other factor to 0. - a = boxInfinity(a); - b = boxInfinity(b); - c = changeNaNtoZero(c); - d = changeNaNtoZero(d); - recalc = true; - } - if ((Double.isInfinite(c) || Double.isInfinite(d)) && - isNotZero(a, b)) { - // The other complex is infinite. - // "Box" the infinity and change NaNs in the other factor to 0. - c = boxInfinity(c); - d = boxInfinity(d); - a = changeNaNtoZero(a); - b = changeNaNtoZero(b); - recalc = true; - } - if (!recalc && (Double.isInfinite(ac) || Double.isInfinite(bd) || - Double.isInfinite(ad) || Double.isInfinite(bc))) { - // The result overflowed to infinity. - // Recover infinities from overflow by changing NaNs to 0 ... - a = changeNaNtoZero(a); - b = changeNaNtoZero(b); - c = changeNaNtoZero(c); - d = changeNaNtoZero(d); - recalc = true; - } - if (recalc) { - x = Double.POSITIVE_INFINITY * (a * c - b * d); - y = Double.POSITIVE_INFINITY * (a * d + b * c); - } - } - return new Complex(x, y); - } - - /** - * Box values for the real or imaginary component of an infinite complex number. - * Any infinite value will be returned as one. Non-infinite values will be returned as zero. - * The sign is maintained. - * - * <pre> - * inf = 1 - * -inf = -1 - * x = 0 - * -x = -0 - * </pre> - * - * @param component the component - * @return The boxed value - */ - private static double boxInfinity(double component) { - return Math.copySign(Double.isInfinite(component) ? 1.0 : 0.0, component); - } - - /** - * Checks if the complex number is not zero. - * - * @param real the real component - * @param imaginary the imaginary component - * @return true if the complex is not zero - */ - private static boolean isNotZero(double real, double imaginary) { - // The use of equals is deliberate. - // This method must distinguish NaN from zero thus ruling out: - // (real != 0.0 || imaginary != 0.0) - return !(real == 0.0 && imaginary == 0.0); - } - - /** - * Change NaN to zero preserving the sign; otherwise return the value. - * - * @param value the value - * @return The new value - */ - private static double changeNaNtoZero(double value) { - return Double.isNaN(value) ? Math.copySign(0.0, value) : value; + return ComplexFunctions.multiply(real, imaginary, factor.real, factor.imaginary, Complex::ofCartesian); } /** @@ -901,90 +773,7 @@ public final class Complex implements Serializable { * @see <a href="http://mathworld.wolfram.com/ComplexDivision.html">Complex Division</a> */ public Complex divide(Complex divisor) { - return divide(real, imaginary, divisor.real, divisor.imaginary); - } - - /** - * Returns a {@code Complex} whose value is: - * <pre> - * <code> - * a + i b (ac + bd) + i (bc - ad) - * ------- = ----------------------- - * c + i d c<sup>2</sup> + d<sup>2</sup> - * </code> - * </pre> - * - * <p>Recalculates to recover infinities as specified in C99 - * standard G.5.1. Method is fully in accordance with - * C++11 standards for complex numbers.</p> - * - * <p>Note: In the event of divide by zero this method produces the same result - * as dividing by a real-only zero using {@link #divide(double)}. - * - * @param re1 Real component of first number. - * @param im1 Imaginary component of first number. - * @param re2 Real component of second number. - * @param im2 Imaginary component of second number. - * @return (a + i b) / (c + i d). - * @see <a href="http://mathworld.wolfram.com/ComplexDivision.html">Complex Division</a> - * @see #divide(double) - */ - private static Complex divide(double re1, double im1, double re2, double im2) { - double a = re1; - double b = im1; - double c = re2; - double d = im2; - int ilogbw = 0; - // Get the exponent to scale the divisor parts to the range [1, 2). - final int exponent = getScale(c, d); - if (exponent <= Double.MAX_EXPONENT) { - ilogbw = exponent; - c = Math.scalb(c, -ilogbw); - d = Math.scalb(d, -ilogbw); - } - final double denom = c * c + d * d; - - // Note: Modification from the listing in ISO C99 G.5.1 (8): - // Avoid overflow if a or b are very big. - // Since (c, d) in the range [1, 2) the sum (ac + bd) could overflow - // when (a, b) are both above (Double.MAX_VALUE / 4). The same applies to - // (bc - ad) with large negative values. - // Use the maximum exponent as an approximation to the magnitude. - if (getMaxExponent(a, b) > Double.MAX_EXPONENT - 2) { - ilogbw -= 2; - a /= 4; - b /= 4; - } - - double x = Math.scalb((a * c + b * d) / denom, -ilogbw); - double y = Math.scalb((b * c - a * d) / denom, -ilogbw); - // Recover infinities and zeros that computed as NaN+iNaN - // the only cases are nonzero/zero, infinite/finite, and finite/infinite, ... - if (Double.isNaN(x) && Double.isNaN(y)) { - if (denom == 0.0 && - (!Double.isNaN(a) || !Double.isNaN(b))) { - // nonzero/zero - // This case produces the same result as divide by a real-only zero - // using Complex.divide(+/-0.0) - x = Math.copySign(Double.POSITIVE_INFINITY, c) * a; - y = Math.copySign(Double.POSITIVE_INFINITY, c) * b; - } else if ((Double.isInfinite(a) || Double.isInfinite(b)) && - Double.isFinite(c) && Double.isFinite(d)) { - // infinite/finite - a = boxInfinity(a); - b = boxInfinity(b); - x = Double.POSITIVE_INFINITY * (a * c + b * d); - y = Double.POSITIVE_INFINITY * (b * c - a * d); - } else if ((Double.isInfinite(c) || Double.isInfinite(d)) && - Double.isFinite(a) && Double.isFinite(b)) { - // finite/infinite - c = boxInfinity(c); - d = boxInfinity(d); - x = 0.0 * (a * c + b * d); - y = 0.0 * (b * c - a * d); - } - } - return new Complex(x, y); + return ComplexFunctions.divide(real, imaginary, divisor.real, divisor.imaginary, Complex::ofCartesian); } /** @@ -1907,93 +1696,4 @@ public final class Complex implements Serializable { private static boolean negative(double d) { return ComplexFunctions.negative(d); } - - /** - * Returns a scale suitable for use with {@link Math#scalb(double, int)} to normalise - * the number to the interval {@code [1, 2)}. - * - * <p>The scale is typically the largest unbiased exponent used in the representation of the - * two numbers. In contrast to {@link Math#getExponent(double)} this handles - * sub-normal numbers by computing the number of leading zeros in the mantissa - * and shifting the unbiased exponent. The result is that for all finite, non-zero, - * numbers {@code a, b}, the magnitude of {@code scalb(x, -getScale(a, b))} is - * always in the range {@code [1, 2)}, where {@code x = max(|a|, |b|)}. - * - * <p>This method is a functional equivalent of the c function ilogb(double) adapted for - * two input arguments. - * - * <p>The result is to be used to scale a complex number using {@link Math#scalb(double, int)}. - * Hence the special case of both zero arguments is handled using the return value for NaN - * as zero cannot be scaled. This is different from {@link Math#getExponent(double)} - * or {@link #getMaxExponent(double, double)}. - * - * <p>Special cases: - * - * <ul> - * <li>If either argument is NaN or infinite, then the result is - * {@link Double#MAX_EXPONENT} + 1. - * <li>If both arguments are zero, then the result is - * {@link Double#MAX_EXPONENT} + 1. - * </ul> - * - * @param a the first value - * @param b the second value - * @return The maximum unbiased exponent of the values to be used for scaling - * @see Math#getExponent(double) - * @see Math#scalb(double, int) - * @see <a href="http://www.cplusplus.com/reference/cmath/ilogb/">ilogb</a> - */ - private static int getScale(double a, double b) { - // Only interested in the exponent and mantissa so remove the sign bit - final long x = Double.doubleToRawLongBits(a) & UNSIGN_MASK; - final long y = Double.doubleToRawLongBits(b) & UNSIGN_MASK; - // Only interested in the maximum - final long bits = Math.max(x, y); - // Get the unbiased exponent - int exp = ((int) (bits >>> 52)) - EXPONENT_OFFSET; - - // No case to distinguish nan/inf - // Handle sub-normal numbers - if (exp == Double.MIN_EXPONENT - 1) { - // Special case for zero, return as nan/inf to indicate scaling is not possible - if (bits == 0) { - return Double.MAX_EXPONENT + 1; - } - // A sub-normal number has an exponent below -1022. The amount below - // is defined by the number of shifts of the most significant bit in - // the mantissa that is required to get a 1 at position 53 (i.e. as - // if it were a normal number with assumed leading bit) - final long mantissa = bits & MANTISSA_MASK; - exp -= Long.numberOfLeadingZeros(mantissa << 12); - } - return exp; - } - - /** - * Returns the largest unbiased exponent used in the representation of the - * two numbers. Special cases: - * - * <ul> - * <li>If either argument is NaN or infinite, then the result is - * {@link Double#MAX_EXPONENT} + 1. - * <li>If both arguments are zero or subnormal, then the result is - * {@link Double#MIN_EXPONENT} -1. - * </ul> - * - * <p>This is used by {@link #divide(double, double, double, double)} as - * a simple detection that a number may overflow if multiplied - * by a value in the interval [1, 2). - * - * @param a the first value - * @param b the second value - * @return The maximum unbiased exponent of the values. - * @see Math#getExponent(double) - * @see #divide(double, double, double, double) - */ - private static int getMaxExponent(double a, double b) { - // This could return: - // Math.getExponent(Math.max(Math.abs(a), Math.abs(b))) - // A speed test is required to determine performance. - return Math.max(Math.getExponent(a), Math.getExponent(b)); - } } diff --git a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexBinaryOperator.java b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexBinaryOperator.java new file mode 100644 index 00000000..6714e78e --- /dev/null +++ b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexBinaryOperator.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Represents a binary operation on a Cartesian form of a complex number \( a + ib \) + * where \( a \) and \( b \) are real numbers represented as two {@code double} + * parts. The operation creates a complex number result; the result is supplied + * to a terminating consumer function which may return an object representation + * of the complex result. + * + * <p>This is a functional interface whose functional method is + * {@link #apply(double, double, double, double, ComplexSink)}. + * + * @param <R> The type of the complex result + * @since 1.1 + */ +@FunctionalInterface +public interface ComplexBinaryOperator<R> { + + /** + * Represents an operator that accepts real and imaginary parts of two complex numbers and supplies the complex result to the provided consumer. + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param out Consumer for the complex result. + * @return the object returned by the provided consumer. + */ + R apply(double real1, double imaginary1, double real2, double imaginary2, ComplexSink<R> out); +} diff --git a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java index 460e1f39..6dd614fb 100644 --- a/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java +++ b/commons-numbers-complex/src/main/java/org/apache/commons/numbers/complex/ComplexFunctions.java @@ -95,6 +95,8 @@ public final class ComplexFunctions { private static final double EPSILON = Double.longBitsToDouble((EXPONENT_OFFSET - 53L) << 52); /** Mask to remove the sign bit from a long. */ private static final long UNSIGN_MASK = 0x7fff_ffff_ffff_ffffL; + /** Mask to extract the 52-bit mantissa from a long representation of a double. */ + private static final long MANTISSA_MASK = 0x000f_ffff_ffff_ffffL; /** The multiplier used to split the double value into hi and low parts. This must be odd * and a value of 2^s + 1 in the range {@code p/2 <= s <= p-1} where p is the number of * bits of precision of the floating point number. Here {@code s = 27}.*/ @@ -345,7 +347,7 @@ public final class ComplexFunctions { * * @param real Real part \( a \) of the complex number \( (a +ib) \). * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib) \). - * @param action Consumer for the exponential of the complex number. + * @param action Consumer for the conjugate of the complex number. * @param <R> the return type of the supplied action. * @return the object returned by the supplied action. */ @@ -362,7 +364,7 @@ public final class ComplexFunctions { * * @param real Real part \( a \) of the complex number \( (a +ib) \). * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib) \). - * @param action Consumer for the exponential of the complex number. + * @param action Consumer for the negation of the complex number. * @param <R> the return type of the supplied action. * @return the object returned by the supplied action. */ @@ -382,7 +384,7 @@ public final class ComplexFunctions { * * @param real Real part \( a \) of the complex number \( (a +ib) \). * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib) \). - * @param action Consumer for the exponential of the complex number. + * @param action Consumer for the projection of the complex number. * @param <R> the return type of the supplied action. * @return the object returned by the supplied action. * @see #isInfinite(double, double) @@ -396,6 +398,259 @@ public final class ComplexFunctions { return action.apply(real, imaginary); } + /** + * Returns a {@code Object} whose value is {@code (real1 + real2, imaginary1 + imaginary2)}. + * Implements the formula: + * + * <p>\[ (a + i b) + (c + i d) = (a + c) + i (b + d) \] + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param action Consumer for the addition result. + * @param <R> the return type of the supplied action. + * @return the object returned by the supplied action. + * @see <a href="http://mathworld.wolfram.com/ComplexAddition.html">Complex Addition</a> + */ + public static <R> R add(double real1, double imaginary1, + double real2, double imaginary2, + ComplexSink<R> action) { + return action.apply(real1 + real2, + imaginary1 + imaginary2); + } + + /** + * Returns a {@code Object} whose value is {@code (real1 - real2, imaginary1 - imaginary2)}. + * Implements the formula: + * + * <p>\[ (a + i b) - (c + i d) = (a - c) + i (b - d) \] + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param action Consumer for the subtraction result. + * @param <R> the return type of the supplied action. + * @return the object returned by the supplied action. + * @see <a href="http://mathworld.wolfram.com/ComplexSubtraction.html">Complex Subtraction</a> + */ + public static <R> R subtract(double real1, double imaginary1, + double real2, double imaginary2, + ComplexSink<R> action) { + return action.apply(real1 - real2, + imaginary1 - imaginary2); + } + + /** + * Returns a {@code Object} whose value is {@code (real1 + i*imaginary1) * (real2 + i*imaginary2))}. + * Implements the formula: + * + * <p>\[ (a + i b)(c + i d) = (ac - bd) + i (ad + bc) \] + * + * <p>Recalculates to recover infinities as specified in C99 standard G.5.1. + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param action Consumer for the multiplication result. + * @param <R> the return type of the supplied action. + * @return the object returned by the supplied action. + * @see <a href="http://mathworld.wolfram.com/ComplexMultiplication.html">Complex Muliplication</a> + */ + public static <R> R multiply(double real1, double imaginary1, + double real2, double imaginary2, + ComplexSink<R> action) { + double a = real1; + double b = imaginary1; + double c = real2; + double d = imaginary2; + final double ac = a * c; + final double bd = b * d; + final double ad = a * d; + final double bc = b * c; + double x = ac - bd; + double y = ad + bc; + + // -------------- + // NaN can occur if: + // - any of (a,b,c,d) are NaN (for NaN or Infinite complex numbers) + // - a multiplication of infinity by zero (ac,bd,ad,bc). + // - a subtraction of infinity from infinity (e.g. ac - bd) + // Note that (ac,bd,ad,bc) can be infinite due to overflow. + // + // Detect a NaN result and perform correction. + // + // Modification from the listing in ISO C99 G.5.1 (6) + // Do not correct infinity multiplied by zero. This is left as NaN. + // -------------- + + if (Double.isNaN(x) && Double.isNaN(y)) { + // Recover infinities that computed as NaN+iNaN ... + boolean recalc = false; + if ((Double.isInfinite(a) || Double.isInfinite(b)) && + isNotZero(c, d)) { + // This complex is infinite. + // "Box" the infinity and change NaNs in the other factor to 0. + a = boxInfinity(a); + b = boxInfinity(b); + c = changeNaNtoZero(c); + d = changeNaNtoZero(d); + recalc = true; + } + if ((Double.isInfinite(c) || Double.isInfinite(d)) && + isNotZero(a, b)) { + // The other complex is infinite. + // "Box" the infinity and change NaNs in the other factor to 0. + c = boxInfinity(c); + d = boxInfinity(d); + a = changeNaNtoZero(a); + b = changeNaNtoZero(b); + recalc = true; + } + if (!recalc && (Double.isInfinite(ac) || Double.isInfinite(bd) || + Double.isInfinite(ad) || Double.isInfinite(bc))) { + // The result overflowed to infinity. + // Recover infinities from overflow by changing NaNs to 0 ... + a = changeNaNtoZero(a); + b = changeNaNtoZero(b); + c = changeNaNtoZero(c); + d = changeNaNtoZero(d); + recalc = true; + } + if (recalc) { + x = Double.POSITIVE_INFINITY * (a * c - b * d); + y = Double.POSITIVE_INFINITY * (a * d + b * c); + } + } + return action.apply(x, y); + } + + /** + * Box values for the real or imaginary component of an infinite complex number. + * Any infinite value will be returned as one. Non-infinite values will be returned as zero. + * The sign is maintained. + * + * <pre> + * inf = 1 + * -inf = -1 + * x = 0 + * -x = -0 + * </pre> + * + * @param component the component + * @return The boxed value + */ + private static double boxInfinity(double component) { + return Math.copySign(Double.isInfinite(component) ? 1.0 : 0.0, component); + } + + /** + * Checks if the complex number is not zero. + * + * @param real the real component + * @param imaginary the imaginary component + * @return true if the complex is not zero + */ + private static boolean isNotZero(double real, double imaginary) { + // The use of equals is deliberate. + // This method must distinguish NaN from zero thus ruling out: + // (real != 0.0 || imaginary != 0.0) + return !(real == 0.0 && imaginary == 0.0); + } + + /** + * Change NaN to zero preserving the sign; otherwise return the value. + * + * @param value the value + * @return The new value + */ + private static double changeNaNtoZero(double value) { + return Double.isNaN(value) ? Math.copySign(0.0, value) : value; + } + + /** + * Returns a {@code Object} whose value is {@code (real1 + i*imaginary1)/(real2 + i*imaginary2)}. + * Implements the formula: + * + * <p>\[ \frac{a + i b}{c + i d} = \frac{(ac + bd) + i (bc - ad)}{c^2+d^2} \] + * + * <p>Re-calculates NaN result values to recover infinities as specified in C99 standard G.5.1. + * + * <p>Note: In the event of divide by zero this method produces the same result + * as dividing by a real-only zero using (add divide reference) + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param action Consumer for the division result. + * @param <R> the return type of the supplied action. + * @return the object returned by the supplied action. + * @see <a href="http://mathworld.wolfram.com/ComplexDivision.html">Complex Division</a> + */ + //TODO - add divide reference once moved to ComplexFunctions + public static <R> R divide(double real1, double imaginary1, + double real2, double imaginary2, + ComplexSink<R> action) { + double a = real1; + double b = imaginary1; + double c = real2; + double d = imaginary2; + int ilogbw = 0; + // Get the exponent to scale the divisor parts to the range [1, 2). + final int exponent = getScale(c, d); + if (exponent <= Double.MAX_EXPONENT) { + ilogbw = exponent; + c = Math.scalb(c, -ilogbw); + d = Math.scalb(d, -ilogbw); + } + final double denom = c * c + d * d; + + // Note: Modification from the listing in ISO C99 G.5.1 (8): + // Avoid overflow if a or b are very big. + // Since (c, d) in the range [1, 2) the sum (ac + bd) could overflow + // when (a, b) are both above (Double.MAX_VALUE / 4). The same applies to + // (bc - ad) with large negative values. + // Use the maximum exponent as an approximation to the magnitude. + if (getMaxExponent(a, b) > Double.MAX_EXPONENT - 2) { + ilogbw -= 2; + a /= 4; + b /= 4; + } + + double x = Math.scalb((a * c + b * d) / denom, -ilogbw); + double y = Math.scalb((b * c - a * d) / denom, -ilogbw); + // Recover infinities and zeros that computed as NaN+iNaN + // the only cases are nonzero/zero, infinite/finite, and finite/infinite, ... + if (Double.isNaN(x) && Double.isNaN(y)) { + if (denom == 0.0 && + (!Double.isNaN(a) || !Double.isNaN(b))) { + // nonzero/zero + // This case produces the same result as divide by a real-only zero + // using Complex.divide(+/-0.0) + x = Math.copySign(Double.POSITIVE_INFINITY, c) * a; + y = Math.copySign(Double.POSITIVE_INFINITY, c) * b; + } else if ((Double.isInfinite(a) || Double.isInfinite(b)) && + Double.isFinite(c) && Double.isFinite(d)) { + // infinite/finite + a = boxInfinity(a); + b = boxInfinity(b); + x = Double.POSITIVE_INFINITY * (a * c + b * d); + y = Double.POSITIVE_INFINITY * (b * c - a * d); + } else if ((Double.isInfinite(c) || Double.isInfinite(d)) && + Double.isFinite(a) && Double.isFinite(b)) { + // finite/infinite + c = boxInfinity(c); + d = boxInfinity(d); + x = 0.0 * (a * c + b * d); + y = 0.0 * (b * c - a * d); + } + } + return action.apply(x, y); + } + /** * Returns the * <a href="http://mathworld.wolfram.com/ExponentialFunction.html"> @@ -677,6 +932,45 @@ public final class ComplexFunctions { return action.apply(re, arg(real, imaginary)); } + /** + * Returns the complex power of the first complex number raised to the power of + * second complex number. + * Implements the formula: + * + * <p>\[ z^x = e^{x \ln(z)} \] + * + * <p>If the complex number is zero then this method returns zero if the second complex number is positive + * in the real component and zero in the imaginary component; + * otherwise it returns NaN + iNaN. + * + * @param real1 Real part \( a \) of the first complex number \( (a +ib) \). + * @param imaginary1 Imaginary part \( b \) of the first complex number \( (a +ib) \). + * @param real2 Real part \( a \) of the second complex number \( (a +ib) \). + * @param imaginary2 Imaginary part \( b \) of the second complex number \( (a +ib) \). + * @param action Consumer for the power result. + * @param <R> the return type of the supplied action. + * @return the object returned by the supplied action. + * @see #log(double, double, ComplexSink) + * @see #multiply(double, double, double, double, ComplexSink) + * @see #exp(double, double, ComplexSink) + * @see <a href="http://mathworld.wolfram.com/ComplexExponentiation.html">Complex exponentiation</a> + * @see <a href="http://functions.wolfram.com/ElementaryFunctions/Power/">Power</a> + */ + public static <R> R pow(double real1, double imaginary1, double real2, double imaginary2, ComplexSink<R> action) { + if (real1 == 0 && + imaginary1 == 0) { + // This value is zero. Test the other. + if (real2 > 0 && + imaginary2 == 0) { + // 0 raised to positive number is 0 + return action.apply(0, 0); + } + // 0 raised to anything else is NaN + return action.apply(Double.NaN, Double.NaN); + } + return log(real1, imaginary1, (x, y) -> multiply(x, y, real2, imaginary2, (a, b) -> exp(a, b, action))); + } + /** * Returns the * <a href="http://mathworld.wolfram.com/SquareRoot.html"> @@ -2551,6 +2845,95 @@ public final class ComplexFunctions { return negative(signedValue) ? -magnitude : magnitude; } + /** + * Returns a scale suitable for use with {@link Math#scalb(double, int)} to normalise + * the number to the interval {@code [1, 2)}. + * + * <p>The scale is typically the largest unbiased exponent used in the representation of the + * two numbers. In contrast to {@link Math#getExponent(double)} this handles + * sub-normal numbers by computing the number of leading zeros in the mantissa + * and shifting the unbiased exponent. The result is that for all finite, non-zero, + * numbers {@code a, b}, the magnitude of {@code scalb(x, -getScale(a, b))} is + * always in the range {@code [1, 2)}, where {@code x = max(|a|, |b|)}. + * + * <p>This method is a functional equivalent of the c function ilogb(double) adapted for + * two input arguments. + * + * <p>The result is to be used to scale a complex number using {@link Math#scalb(double, int)}. + * Hence the special case of both zero arguments is handled using the return value for NaN + * as zero cannot be scaled. This is different from {@link Math#getExponent(double)} + * or {@link #getMaxExponent(double, double)}. + * + * <p>Special cases: + * + * <ul> + * <li>If either argument is NaN or infinite, then the result is + * {@link Double#MAX_EXPONENT} + 1. + * <li>If both arguments are zero, then the result is + * {@link Double#MAX_EXPONENT} + 1. + * </ul> + * + * @param a the first value + * @param b the second value + * @return The maximum unbiased exponent of the values to be used for scaling + * @see Math#getExponent(double) + * @see Math#scalb(double, int) + * @see <a href="http://www.cplusplus.com/reference/cmath/ilogb/">ilogb</a> + */ + private static int getScale(double a, double b) { + // Only interested in the exponent and mantissa so remove the sign bit + final long x = Double.doubleToRawLongBits(a) & UNSIGN_MASK; + final long y = Double.doubleToRawLongBits(b) & UNSIGN_MASK; + // Only interested in the maximum + final long bits = Math.max(x, y); + // Get the unbiased exponent + int exp = ((int) (bits >>> 52)) - EXPONENT_OFFSET; + + // No case to distinguish nan/inf + // Handle sub-normal numbers + if (exp == Double.MIN_EXPONENT - 1) { + // Special case for zero, return as nan/inf to indicate scaling is not possible + if (bits == 0) { + return Double.MAX_EXPONENT + 1; + } + // A sub-normal number has an exponent below -1022. The amount below + // is defined by the number of shifts of the most significant bit in + // the mantissa that is required to get a 1 at position 53 (i.e. as + // if it were a normal number with assumed leading bit) + final long mantissa = bits & MANTISSA_MASK; + exp -= Long.numberOfLeadingZeros(mantissa << 12); + } + return exp; + } + + /** + * Returns the largest unbiased exponent used in the representation of the + * two numbers. Special cases: + * + * <ul> + * <li>If either argument is NaN or infinite, then the result is + * {@link Double#MAX_EXPONENT} + 1. + * <li>If both arguments are zero or subnormal, then the result is + * {@link Double#MIN_EXPONENT} -1. + * </ul> + * + * <p>This is used by {@link #divide(double, double, double, double, ComplexSink)} as + * a simple detection that a number may overflow if multiplied + * by a value in the interval [1, 2). + * + * @param a the first value + * @param b the second value + * @return The maximum unbiased exponent of the values. + * @see Math#getExponent(double) + * @see #divide(double, double, double, double, ComplexSink) + */ + private static int getMaxExponent(double a, double b) { + // This could return: + // Math.getExponent(Math.max(Math.abs(a), Math.abs(b))) + // A speed test is required to determine performance. + return Math.max(Math.getExponent(a), Math.getExponent(b)); + } + /** * Checks if both x and y are in the region defined by the minimum and maximum. * diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java index 62dc190e..a56cce8f 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CReferenceTest.java @@ -173,29 +173,6 @@ class CReferenceTest { assertEquals(() -> c + "." + name + "(): imaginary", expected.imag(), z.imag(), maxUlps); } - /** - * Assert the operation on the complex number is equal to the expected value. - * - * <p>The results are considered equal within the provided units of least - * precision. The maximum count of numbers allowed between the two values is - * {@code maxUlps - 1}. - * - * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal. - * - * @param c Input number. - * @param name the operation name - * @param operation the operation - * @param expected Expected result. - * @param maxUlps the maximum units of least precision between the two values - */ - static void assertComplex(Complex c, - String name, UnaryOperator<Complex> operation, - Complex expected, long maxUlps) { - final Complex z = operation.apply(c); - assertEquals(() -> c + "." + name + "(): real", expected.real(), z.real(), maxUlps); - assertEquals(() -> c + "." + name + "(): imaginary", expected.imag(), z.imag(), maxUlps); - } - /** * Assert the operation on the complex numbers is equal to the expected value. * @@ -205,17 +182,22 @@ class CReferenceTest { * * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal. * - * @param c1 First number. - * @param c2 Second number. - * @param name the operation name - * @param operation the operation - * @param expected Expected real part. - * @param maxUlps the maximum units of least precision between the two values + * <p>Assert the operation on the complex numbers is <em>exactly</em> equal to the operation on + * complex real and imaginary parts of two complex numbers. + * + * @param c1 First input number. + * @param c2 Second input number. + * @param name Operation name. + * @param operation1 Operation on the Complex objects. + * @param operation2 Operation on the complex real and imaginary parts of two complex numbers. + * @param expected Expected result. + * @param maxUlps Maximum units of least precision between the two values. */ static void assertComplex(Complex c1, Complex c2, - String name, BiFunction<Complex, Complex, Complex> operation, + String name, BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2, Complex expected, long maxUlps) { - final Complex z = operation.apply(c1, c2); + final Complex z = TestUtils.assertSame(c1, c2, name, operation1, operation2); assertEquals(() -> c1 + "." + name + c2 + ": real", expected.real(), z.real(), maxUlps); assertEquals(() -> c1 + "." + name + c2 + ": imaginary", expected.imag(), z.imag(), maxUlps); } @@ -241,16 +223,19 @@ class CReferenceTest { /** * Assert the operation using the data loaded from test resources. * - * @param name the operation name - * @param operation the operation + * @param name Operation name. + * @param operation1 Operation on the Complex objects. + * @param operation2 Operation on the complex real and imaginary parts of two complex numbers. * @param maxUlps the maximum units of least precision between the two values */ private static void assertBiOperation(String name, - BiFunction<Complex, Complex, Complex> operation, long maxUlps) { + BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2, + long maxUlps) { final List<Complex[]> data = loadTestData(name); final long ulps = getTestUlps(maxUlps); for (final Complex[] triple : data) { - assertComplex(triple[0], triple[1], name, operation, triple[2], ulps); + assertComplex(triple[0], triple[1], name, operation1, operation2, triple[2], ulps); } } @@ -352,16 +337,16 @@ class CReferenceTest { @Test void testMultiply() { - assertBiOperation("multiply", Complex::multiply, 0); + assertBiOperation("multiply", Complex::multiply, ComplexFunctions::multiply, 0); } @Test void testDivide() { - assertBiOperation("divide", Complex::divide, 7); + assertBiOperation("divide", Complex::divide, ComplexFunctions::divide, 7); } @Test void testPowComplex() { - assertBiOperation("pow", Complex::pow, 9); + assertBiOperation("pow", Complex::pow, ComplexFunctions::pow, 9); } } diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java index 85bc5590..620e3a92 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/CStandardTest.java @@ -203,17 +203,23 @@ class CStandardTest { /** * 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 + * <p>Assert the operation on the complex numbers is <em>exactly</em> equal to the operation on + * complex real and imaginary parts of two complex numbers. + * + * @param c1 First input complex number. + * @param c2 Second input complex number. + * @param operation1 Operation on the Complex objects. + * @param operation2 Operation on the complex real and imaginary parts of two complex numbers. + * @param operationName Operation name. + * @param expected Expected complex number. + * @param expectedName 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); + BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2, + String operationName, Predicate<Complex> expected, + String expectedName) { + final Complex z = TestUtils.assertSame(c1, c2, operationName, operation1, operation2); Assertions.assertTrue(expected.test(z), () -> String.format("%s expected: %s %s %s = %s", expectedName, c1, operationName, c2, z)); } @@ -844,18 +850,18 @@ class CStandardTest { // 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"); + assertOperation(z, w, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(w, z, Complex::multiply, ComplexFunctions::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"); + assertOperation(z, w, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(w, z, Complex::multiply, ComplexFunctions::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"); + assertOperation(z, w, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isNaN, "NaN"); + assertOperation(w, z, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isNaN, "NaN"); } } @@ -871,16 +877,16 @@ class CStandardTest { // 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"); + assertOperation(z, NAN, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isNaN, "NaN"); + assertOperation(NAN, z, Complex::multiply, ComplexFunctions::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"); + assertOperation(maxMax, maxMax, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(maxNan, maxNan, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(nanMax, maxNan, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(maxNan, nanMax, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); + assertOperation(nanMax, nanMax, Complex::multiply, ComplexFunctions::multiply, "*", Complex::isInfinite, "Inf"); } /** @@ -899,14 +905,14 @@ class CStandardTest { // 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"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isInfinite, "Inf"); } for (final Complex w : zeroFinites) { - assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isInfinite, "Inf"); } // Check inf/inf cannot be done for (final Complex w : infinites) { - assertOperation(z, w, Complex::divide, "/", Complex::isNaN, "NaN"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isNaN, "NaN"); } } @@ -914,7 +920,7 @@ class CStandardTest { // 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"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", CStandardTest::isZero, "Zero"); } } @@ -922,10 +928,10 @@ class CStandardTest { // 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"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isInfinite, "Inf"); } for (final Complex z : infinites) { - assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isInfinite, "Inf"); } } @@ -934,19 +940,19 @@ class CStandardTest { // infinite. for (final Complex w : nans) { for (final Complex z : finites) { - assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)"); + assertOperation(z, w, Complex::divide, ComplexFunctions::divide, "/", c -> NAN.equals(c), "(NaN,NaN)"); } for (final Complex z : infinites) { - assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)"); + assertOperation(z, w, Complex::divide, ComplexFunctions::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"); + assertOperation(NAN, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isNaN, "NaN"); } for (final Complex w : infinites) { - assertOperation(NAN, w, Complex::divide, "/", Complex::isNaN, "NaN"); + assertOperation(NAN, w, Complex::divide, ComplexFunctions::divide, "/", Complex::isNaN, "NaN"); } } diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java index 72cc88d3..6759d35e 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexEdgeCaseTest.java @@ -96,19 +96,24 @@ class ComplexEdgeCaseTest { * * <p>The results are considered equal if there are no floating-point values between them. * + * <p>Assert the operation on the complex numbers is <em>exactly</em> equal to the operation on + * complex real and imaginary parts. + * * @param a Real part of first number. * @param b Imaginary part of first number. * @param c Real part of second number. * @param d Imaginary part of second number. * @param name The operation name. - * @param operation The operation. + * @param operation1 Operation on the Complex object. + * @param operation2 Operation on the complex real and imaginary parts. * @param x Expected real part. * @param y Expected imaginary part. */ private static void assertComplex(double a, double b, double c, double d, - String name, BiFunction<Complex, Complex, Complex> operation, + String name, BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2, double x, double y) { - assertComplex(a, b, c, d, name, operation, x, y, 1); + assertComplex(a, b, c, d, name, operation1, operation2, x, y, 1); } /** @@ -118,23 +123,28 @@ class ComplexEdgeCaseTest { * precision. The maximum count of numbers allowed between the two values is * {@code maxUlps - 1}. * + * <p>Assert the operation on the complex numbers is <em>exactly</em> equal to the operation on + * complex real and imaginary parts. + * * @param a Real part of first number. * @param b Imaginary part of first number. * @param c Real part of second number. * @param d Imaginary part of second number. * @param name The operation name - * @param operation the operation + * @param operation1 Operation on the Complex objects. + * @param operation2 Operation on the complex real and imaginary parts. * @param x Expected real part. * @param y Expected imaginary part. * @param maxUlps the maximum units of least precision between the two values */ private static void assertComplex(double a, double b, double c, double d, - String name, BiFunction<Complex, Complex, Complex> operation, + String name, BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2, double x, double y, long maxUlps) { final Complex c1 = Complex.ofCartesian(a, b); final Complex c2 = Complex.ofCartesian(c, d); final Complex e = Complex.ofCartesian(x, y); - CReferenceTest.assertComplex(c1, c2, name, operation, e, maxUlps); + CReferenceTest.assertComplex(c1, c2, name, operation1, operation2, e, maxUlps); } @Test @@ -657,7 +667,8 @@ class ComplexEdgeCaseTest { @Test void testDivide() { final String name = "divide"; - final BiFunction<Complex, Complex, Complex> operation = Complex::divide; + final BiFunction<Complex, Complex, Complex> operation1 = Complex::divide; + final ComplexBinaryOperator<ComplexNumber> operation2 = ComplexFunctions::divide; // Should be able to divide by a complex whose absolute (c*c+d*d) // overflows or underflows including all sub-normal numbers. @@ -674,30 +685,34 @@ class ComplexEdgeCaseTest { // In other words the result is (x+iy) / (x+iy) = (1+i0) // The result is the same if imaginary is zero (i.e. a real only divide) - assertComplex(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, name, operation, 1.0, 0.0); - assertComplex(Double.MAX_VALUE, 0.0, Double.MAX_VALUE, 0.0, name, operation, 1.0, 0.0); + assertComplex(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, name, operation1, operation2, 1.0, + 0.0); + assertComplex(Double.MAX_VALUE, 0.0, Double.MAX_VALUE, 0.0, name, operation1, operation2, 1.0, 0.0); - assertComplex(1.0, 1.0, 1.0, 1.0, name, operation, 1.0, 0.0); - assertComplex(1.0, 0.0, 1.0, 0.0, name, operation, 1.0, 0.0); + assertComplex(1.0, 1.0, 1.0, 1.0, name, operation1, operation2, 1.0, 0.0); + assertComplex(1.0, 0.0, 1.0, 0.0, name, operation1, operation2, 1.0, 0.0); // Should work for all small values x = Double.MIN_NORMAL; while (x != 0) { - assertComplex(x, x, x, x, name, operation, 1.0, 0.0); - assertComplex(x, 0, x, 0, name, operation, 1.0, 0.0); + assertComplex(x, x, x, x, name, operation1, operation2, 1.0, 0.0); + assertComplex(x, 0, x, 0, name, operation1, operation2, 1.0, 0.0); x /= 2; } // Some cases of not self-divide - assertComplex(1, 1, Double.MIN_VALUE, Double.MIN_VALUE, name, operation, inf, 0); + assertComplex(1, 1, Double.MIN_VALUE, Double.MIN_VALUE, name, operation1, operation2, inf, 0); // As computed by GNU g++ - assertComplex(Double.MIN_NORMAL, Double.MIN_NORMAL, Double.MIN_VALUE, Double.MIN_VALUE, name, operation, 4503599627370496L, 0); - assertComplex(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_NORMAL, Double.MIN_NORMAL, name, operation, 2.2204460492503131e-16, 0); + assertComplex(Double.MIN_NORMAL, Double.MIN_NORMAL, Double.MIN_VALUE, Double.MIN_VALUE, name, operation1, operation2, + 4503599627370496L, 0); + assertComplex(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_NORMAL, Double.MIN_NORMAL, name, operation1, operation2, + 2.2204460492503131e-16, 0); } @Test void testPow() { final String name = "pow"; - final BiFunction<Complex, Complex, Complex> operation = Complex::pow; + final BiFunction<Complex, Complex, Complex> operation1 = Complex::pow; + final ComplexBinaryOperator<ComplexNumber> operation2 = ComplexFunctions::pow; // pow(Complex) is log().multiply(Complex).exp() // All are overflow safe and handle infinities as defined in the C99 standard. @@ -706,15 +721,15 @@ class ComplexEdgeCaseTest { // using other library implementations. // Test NaN - assertComplex(1, 1, nan, nan, name, operation, nan, nan); - assertComplex(nan, nan, 1, 1, name, operation, nan, nan); - assertComplex(nan, 1, 1, 1, name, operation, nan, nan); - assertComplex(1, nan, 1, 1, name, operation, nan, nan); - assertComplex(1, 1, nan, 1, name, operation, nan, nan); - assertComplex(1, 1, 1, nan, name, operation, nan, nan); + assertComplex(1, 1, nan, nan, name, operation1, operation2, nan, nan); + assertComplex(nan, nan, 1, 1, name, operation1, operation2, nan, nan); + assertComplex(nan, 1, 1, 1, name, operation1, operation2, nan, nan); + assertComplex(1, nan, 1, 1, name, operation1, operation2, nan, nan); + assertComplex(1, 1, nan, 1, name, operation1, operation2, nan, nan); + assertComplex(1, 1, 1, nan, name, operation1, operation2, nan, nan); // Test overflow. - assertComplex(Double.MAX_VALUE, 1, 2, 2, name, operation, inf, -inf); - assertComplex(1, Double.MAX_VALUE, 2, 2, name, operation, -inf, inf); + assertComplex(Double.MAX_VALUE, 1, 2, 2, name, operation1, operation2, inf, -inf); + assertComplex(1, Double.MAX_VALUE, 2, 2, name, operation1, operation2, -inf, inf); } } diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java index 23665b71..02653eb6 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java @@ -531,7 +531,7 @@ class ComplexTest { void testAdd() { final Complex x = Complex.ofCartesian(3.0, 4.0); final Complex y = Complex.ofCartesian(5.0, 6.0); - final Complex z = x.add(y); + final Complex z = TestUtils.assertSame(x, y, "add", Complex::add, ComplexFunctions::add); Assertions.assertEquals(8.0, z.getReal()); Assertions.assertEquals(10.0, z.getImaginary()); } @@ -540,12 +540,13 @@ class ComplexTest { void testAddInf() { Complex x = Complex.ofCartesian(1, 1); final Complex z = Complex.ofCartesian(inf, 0); - final Complex w = x.add(z); + final Complex w = TestUtils.assertSame(x, z, "add", Complex::add, ComplexFunctions::add); Assertions.assertEquals(1, w.getImaginary()); Assertions.assertEquals(inf, w.getReal()); x = Complex.ofCartesian(neginf, 0); - Assertions.assertTrue(Double.isNaN(x.add(z).getReal())); + final Complex result = TestUtils.assertSame(x, z, "add", Complex::add, ComplexFunctions::add); + Assertions.assertTrue(Double.isNaN(result.getReal())); } @Test @@ -644,7 +645,7 @@ class ComplexTest { void testSubtract() { final Complex x = Complex.ofCartesian(3.0, 4.0); final Complex y = Complex.ofCartesian(5.0, 7.0); - final Complex z = x.subtract(y); + final Complex z = TestUtils.assertSame(x, y, "subtract", Complex::subtract, ComplexFunctions::subtract); Assertions.assertEquals(-2.0, z.getReal()); Assertions.assertEquals(-3.0, z.getImaginary()); } @@ -653,11 +654,11 @@ class ComplexTest { void testSubtractInf() { final Complex x = Complex.ofCartesian(3.0, 4.0); final Complex y = Complex.ofCartesian(inf, 7.0); - Complex z = x.subtract(y); + Complex z = TestUtils.assertSame(x, y, "subtract", Complex::subtract, ComplexFunctions::subtract); Assertions.assertEquals(neginf, z.getReal()); Assertions.assertEquals(-3.0, z.getImaginary()); - z = y.subtract(y); + z = TestUtils.assertSame(y, y, "subtract", Complex::subtract, ComplexFunctions::subtract); Assertions.assertEquals(nan, z.getReal()); Assertions.assertEquals(0.0, z.getImaginary()); } @@ -844,22 +845,27 @@ class ComplexTest { void testMultiply() { final Complex x = Complex.ofCartesian(3.0, 4.0); final Complex y = Complex.ofCartesian(5.0, 6.0); - final Complex z = x.multiply(y); + final Complex z = TestUtils.assertSame(x, y, "multiply", Complex::multiply, ComplexFunctions::multiply); Assertions.assertEquals(-9.0, z.getReal()); Assertions.assertEquals(38.0, z.getImaginary()); } @Test void testMultiplyInfInf() { - final Complex z = infInf.multiply(infInf); + final Complex z = TestUtils.assertSame(infInf, infInf, "multiply", Complex::multiply, ComplexFunctions::multiply); // Assert.assertTrue(z.isNaN()); // MATH-620 Assertions.assertTrue(TestUtils.assertSame(z, "isInfinite", Complex::isInfinite, ComplexFunctions::isInfinite)); // Expected results from g++: - Assertions.assertEquals(Complex.ofCartesian(nan, inf), infInf.multiply(infInf)); - Assertions.assertEquals(Complex.ofCartesian(inf, nan), infInf.multiply(infNegInf)); - Assertions.assertEquals(Complex.ofCartesian(-inf, nan), infInf.multiply(negInfInf)); - Assertions.assertEquals(Complex.ofCartesian(nan, -inf), infInf.multiply(negInfNegInf)); + assertMultiply(Complex.ofCartesian(nan, inf), infInf, infInf); + assertMultiply(Complex.ofCartesian(inf, nan), infInf, infNegInf); + assertMultiply(Complex.ofCartesian(-inf, nan), infInf, negInfInf); + assertMultiply(Complex.ofCartesian(nan, -inf), infInf, negInfNegInf); + } + + private static void assertMultiply(Complex expected, Complex input1, Complex input2) { + final Complex actual = TestUtils.assertSame(input1, input2, "multiply", Complex::multiply, ComplexFunctions::multiply); + TestUtils.assertSame(expected, actual); } @Test @@ -1094,7 +1100,7 @@ class ComplexTest { void testDivide() { final Complex x = Complex.ofCartesian(3.0, 4.0); final Complex y = Complex.ofCartesian(5.0, 6.0); - final Complex z = x.divide(y); + final Complex z = TestUtils.assertSame(x, y, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertEquals(39.0 / 61.0, z.getReal()); Assertions.assertEquals(2.0 / 61.0, z.getImaginary()); } @@ -1102,35 +1108,35 @@ class ComplexTest { @Test void testDivideZero() { final Complex x = Complex.ofCartesian(3.0, 4.0); - final Complex z = x.divide(Complex.ZERO); + final Complex z = TestUtils.assertSame(x, Complex.ZERO, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertEquals(INF, z); } @Test void testDivideZeroZero() { final Complex x = Complex.ofCartesian(0.0, 0.0); - final Complex z = x.divide(Complex.ZERO); + final Complex z = TestUtils.assertSame(x, Complex.ZERO, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertEquals(NAN, z); } @Test void testDivideNaN() { final Complex x = Complex.ofCartesian(3.0, 4.0); - final Complex z = x.divide(NAN); + final Complex z = TestUtils.assertSame(x, NAN, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertTrue(TestUtils.assertSame(z, "isNaN", Complex::isNaN, ComplexFunctions::isNaN)); } @Test void testDivideNanInf() { - Complex z = oneInf.divide(Complex.ONE); + Complex z = TestUtils.assertSame(oneInf, Complex.ONE, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertTrue(Double.isNaN(z.getReal())); Assertions.assertEquals(inf, z.getImaginary()); - z = negInfNegInf.divide(oneNan); + z = TestUtils.assertSame(negInfNegInf, oneNan, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertTrue(Double.isNaN(z.getReal())); Assertions.assertTrue(Double.isNaN(z.getImaginary())); - z = negInfInf.divide(Complex.ONE); + z = TestUtils.assertSame(negInfInf, Complex.ONE, "divide", Complex::divide, ComplexFunctions::divide); Assertions.assertTrue(Double.isInfinite(z.getReal())); Assertions.assertTrue(Double.isInfinite(z.getImaginary())); } @@ -1401,7 +1407,7 @@ class ComplexTest { final Complex c = Complex.ofCartesian(a, b); for (final double arg : arguments) { final Complex y = c.divideImaginary(arg); - Complex z = c.divide(ofImaginary(arg)); + Complex z = TestUtils.assertSame(c, ofImaginary(arg), "divide", Complex::divide, ComplexFunctions::divide); final boolean expectedFailure = (expectedFailures & 0x1) == 1; expectedFailures >>>= 1; // If divide by zero then the divide(Complex) method matches divide by real. @@ -1451,7 +1457,8 @@ class ComplexTest { final Complex x = Complex.ofCartesian(3, 4); final double yDouble = 5.0; final Complex yComplex = ofReal(yDouble); - Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble)); + final Complex expected = TestUtils.assertSame(x, yComplex, "pow", Complex::pow, ComplexFunctions::pow); + Assertions.assertEquals(expected, x.pow(yDouble)); } @Test @@ -1459,7 +1466,7 @@ class ComplexTest { // Hits the edge case when real == 0 but imaginary != 0 final Complex x = Complex.ofCartesian(0, 1); final Complex z = Complex.ofCartesian(2, 3); - final Complex c = x.pow(z); + final Complex c = TestUtils.assertSame(x, z, "pow", Complex::pow, ComplexFunctions::pow); // Answer from g++ Assertions.assertEquals(-0.008983291021129429, c.getReal()); Assertions.assertEquals(1.1001358594835313e-18, c.getImaginary()); @@ -1476,7 +1483,7 @@ class ComplexTest { private static void assertPowComplexZeroBase(double re, double im, Complex expected) { final Complex z = Complex.ofCartesian(re, im); - final Complex c = Complex.ZERO.pow(z); + final Complex c = TestUtils.assertSame(Complex.ZERO, z, "pow", Complex::pow, ComplexFunctions::pow); Assertions.assertEquals(expected, c); } @@ -1507,7 +1514,8 @@ class ComplexTest { final Complex x = NAN; final double yDouble = 5.0; final Complex yComplex = ofReal(yDouble); - Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble)); + final Complex expected = TestUtils.assertSame(x, yComplex, "pow", Complex::pow, ComplexFunctions::pow); + Assertions.assertEquals(expected, x.pow(yDouble)); } @Test @@ -1515,7 +1523,8 @@ class ComplexTest { final Complex x = Complex.ofCartesian(3, 4); final double yDouble = Double.NaN; final Complex yComplex = ofReal(yDouble); - Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble)); + final Complex expected = TestUtils.assertSame(x, yComplex, "pow", Complex::pow, ComplexFunctions::pow); + Assertions.assertEquals(expected, x.pow(yDouble)); } @Test diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java index 63556769..e67bec5f 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/TestUtils.java @@ -26,6 +26,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.DoubleBinaryOperator; import java.util.function.Predicate; @@ -432,6 +433,30 @@ public final class TestUtils { return z; } + /** + * Assert the operation on the complex numbers is <em>exactly</em> equal to the operation on + * complex real and imaginary parts of two complex numbers. + * + * @param c1 First input complex number. + * @param c2 Second input complex number. + * @param name Operation name. + * @param operation1 Operation on the Complex objects. + * @param operation2 Operation on the complex real and imaginary parts of two complex numbers. + * @return Result complex number from the given operation. + */ + public static Complex assertSame(Complex c1, + Complex c2, + String name, + BiFunction<Complex, Complex, Complex> operation1, + ComplexBinaryOperator<ComplexNumber> operation2) { + final Complex z = operation1.apply(c1, c2); + // Test operation2 produces the exact same result + final ComplexNumber z2 = operation2.apply(c1.real(), c1.imag(), c2.real(), c2.imag(), ComplexNumber::new); + Assertions.assertEquals(z.real(), z2.getReal(), () -> "Binary operator mismatch: " + name + " real"); + Assertions.assertEquals(z.imag(), z2.getImaginary(), () -> "Binary operator mismatch: " + name + " imaginary"); + return z; + } + /** * Assert the operation on the complex number is <em>exactly</em> equal to the operation on * complex real and imaginary parts.