You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2019/12/04 17:57:10 UTC
[commons-numbers] 02/02: Complex: Increase ISO C99 compliance.
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
commit 152c0eb8c715964e2f35091e374add68ea6bb887
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Dec 4 17:52:47 2019 +0000
Complex: Increase ISO C99 compliance.
Explicit testing of the conjugate equalities. These are implemented for
zero and +/- infinity for the imaginary part.
---
.../apache/commons/numbers/complex/Complex.java | 579 +++++++++++---------
.../commons/numbers/complex/CStandardTest.java | 600 ++++++++++++++-------
2 files changed, 739 insertions(+), 440 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 d218a6a..4ced624 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
@@ -882,34 +882,53 @@ public final class Complex implements Serializable {
* @return the inverse cosine of this complex number.
*/
public Complex acos() {
- if (real == 0 &&
- Double.isNaN(imaginary)) {
- return new Complex(PI_OVER_2, Double.NaN);
- } else if (neitherInfiniteNorZeroNorNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(PI_OVER_2, Double.NEGATIVE_INFINITY);
- } else if (real == Double.NEGATIVE_INFINITY &&
- imaginary == 1) {
- return new Complex(Math.PI, Double.NEGATIVE_INFINITY);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == 1) {
- return new Complex(0, Double.NEGATIVE_INFINITY);
- } else if (real == Double.NEGATIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(PI_3_OVER_4, Double.NEGATIVE_INFINITY);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(PI_OVER_4, Double.NEGATIVE_INFINITY);
- } else if (Double.isInfinite(real) &&
- Double.isNaN(imaginary)) {
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ // Special case for zero
+ if (real == 0 && imaginary == 0) {
+ return new Complex(PI_OVER_2, Math.copySign(0, -imaginary));
+ }
+ // ISO C99: Preserve the equality
+ // acos(conj(z)) = conj(acos(z))
+ Complex z;
+ ComplexConstructor constructor;
+ if (negative(imaginary)) {
+ z = conj();
+ constructor = Complex::ofCartesianConjugate;
+ } else {
+ z = this;
+ constructor = Complex::ofCartesian;
+ }
+ return z.add(z.sqrt1z().multiplyByI()).log().multiplyByNegI(constructor);
+ }
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(PI_OVER_2, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+ }
+ // imaginary is NaN
+ // Special case for real == 0
+ return real == 0 ? new Complex(PI_OVER_2, Double.NaN) : NAN;
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ final double re = real == Double.NEGATIVE_INFINITY ? Math.PI : 0;
+ return new Complex(re, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+ }
+ if (Double.isInfinite(imaginary)) {
+ final double re = real == Double.NEGATIVE_INFINITY ? PI_3_OVER_4 : PI_OVER_4;
+ return new Complex(re, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+ }
+ // imaginary is NaN
// Swap real and imaginary
return new Complex(Double.NaN, real);
- } else if (Double.isNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.NaN, Double.NEGATIVE_INFINITY);
}
- return add(sqrt1z().multiplyByI()).log().multiplyByNegI();
+ // real is NaN
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.NaN, -imaginary);
+ }
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
+
/**
* Compute the
* <a href="http://mathworld.wolfram.com/InverseSine.html">
@@ -974,6 +993,16 @@ public final class Complex implements Serializable {
}
/**
+ * Multiply the Complex by -I and create the result using the constructor.
+ *
+ * @param constructor Constructor
+ * @return the result (-iz)
+ */
+ private Complex multiplyByNegI(ComplexConstructor constructor) {
+ return constructor.create(imaginary, -real);
+ }
+
+ /**
* Compute the
* <a href="http://mathworld.wolfram.com/InverseHyperbolicSine.html">
* inverse hyperbolic sine</a> of this complex number.
@@ -985,26 +1014,43 @@ public final class Complex implements Serializable {
* @return the inverse hyperbolic sine of this complex number
*/
public Complex asinh() {
- if (neitherInfiniteNorZeroNorNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_2);
- } else if (real == Double.POSITIVE_INFINITY &&
- Double.isFinite(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, 0);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_4);
- } else if (real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
- } else if (Double.isNaN(real) &&
- imaginary == 0) {
- return new Complex(Double.NaN, 0);
- } else if (Double.isNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ // Special case for zero
+ if (real == 0 && imaginary == 0) {
+ return this;
+ }
+ // ISO C99: Preserve the equality
+ // asinh(conj(z)) = conj(asinh(z))
+ final Complex z = negative(imaginary) ? conjugate() : this;
+ final Complex result = z.square().add(ONE).sqrt().add(z).log();
+ return z == this ? result : result.conjugate();
+ }
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(PI_OVER_2, imaginary));
+ }
+ // imaginary is NaN
+ return NAN;
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ return new Complex(real, Math.copySign(0, imaginary));
+ }
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(real, Math.copySign(PI_OVER_4, imaginary));
+ }
+ // imaginary is NaN
+ return new Complex(real, Double.NaN);
+ }
+ // real is NaN
+ if (imaginary == 0) {
+ return new Complex(Double.NaN, Math.copySign(0, imaginary));
+ }
+ if (Double.isInfinite(imaginary)) {
return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
}
- return square().add(ONE).sqrt().add(this).log();
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
/**
@@ -1019,29 +1065,44 @@ public final class Complex implements Serializable {
* @return the inverse hyperbolic tangent of this complex number
*/
public Complex atanh() {
- if (real == 0 &&
- Double.isNaN(imaginary)) {
- return new Complex(0, Double.NaN);
- } else if (neitherInfiniteNorZeroNorNaN(real) &&
- imaginary == 0) {
- return new Complex(Double.POSITIVE_INFINITY, 0);
- } else if (neitherInfiniteNorZeroNorNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(0, PI_OVER_2);
- } else if (real == Double.POSITIVE_INFINITY &&
- neitherInfiniteNorZeroNorNaN(imaginary)) {
- return new Complex(0, PI_OVER_2);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(0, PI_OVER_2);
- } else if (real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) {
- return new Complex(0, Double.NaN);
- } else if (Double.isNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(0, PI_OVER_2);
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ // Special case for zero
+ if (imaginary == 0) {
+ if (real == 0) {
+ return this;
+ }
+ if (Math.abs(real) == 1) {
+ // raises the ‘‘divide-by-zero’’ floating-point exception.
+ return new Complex(Math.copySign(Double.POSITIVE_INFINITY, real), imaginary);
+ }
+ }
+ // ISO C99: Preserve the equality
+ // atanh(conj(z)) = conj(atanh(z))
+ final Complex z = negative(imaginary) ? conjugate() : this;
+ final Complex result = z.add(ONE).divide(ONE.subtract(z)).log().multiply(0.5);
+ return z == this ? result : result.conjugate();
+ }
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Math.copySign(0, real), Math.copySign(PI_OVER_2, imaginary));
+ }
+ // imaginary is NaN
+ // Special case for real == 0
+ return real == 0 ? new Complex(real, Double.NaN) : NAN;
}
- return add(ONE).divide(ONE.subtract(this)).log().multiply(0.5);
+ if (Double.isInfinite(real)) {
+ if (Double.isNaN(imaginary)) {
+ return new Complex(Math.copySign(0, real), Double.NaN);
+ }
+ // imaginary is finite or infinite
+ return new Complex(Math.copySign(0, real), Math.copySign(PI_OVER_2, imaginary));
+ }
+ // real is NaN
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(0, Math.copySign(PI_OVER_2, imaginary));
+ }
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
/**
@@ -1057,47 +1118,41 @@ public final class Complex implements Serializable {
*/
public Complex acosh() {
if (Double.isFinite(real)) {
- if (real == 0 &&
- imaginary == 0) {
- return new Complex(0, PI_OVER_2);
- }
- if (imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_2);
- }
- if (Double.isNaN(imaginary)) {
- return NAN;
- }
- } else if (Double.isInfinite(real)) {
- if (real == Double.NEGATIVE_INFINITY &&
- positiveSignedFinite(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, Math.PI);
- }
- if (real == Double.POSITIVE_INFINITY &&
- positiveSignedFinite(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, 0);
+ if (Double.isFinite(imaginary)) {
+ // Special case for zero
+ if (real == 0 && imaginary == 0) {
+ return new Complex(real, Math.copySign(PI_OVER_2, imaginary));
+ }
+ // ISO C99: Preserve the equality
+ // acosh(conj(z)) = conj(acosh(z))
+ final Complex z = negative(imaginary) ? conjugate() : this;
+ final Complex result = z.square().subtract(ONE).sqrt().add(z).log();
+ return z == this ? result : result.conjugate();
}
- if (real == Double.NEGATIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_3_OVER_4);
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(PI_OVER_2, imaginary));
}
- if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_4);
- }
- if (Double.isNaN(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
- }
- } else if (Double.isNaN(real)) {
- if (imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
+ // imaginary is NaN
+ return NAN;
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ final double im = real == Double.NEGATIVE_INFINITY ? Math.PI : 0;
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(im, imaginary));
}
- if (Double.isFinite(imaginary) || Double.isNaN(imaginary)) {
- // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
- // No condition for imaginary as negative infinity
- return NAN;
+ if (Double.isInfinite(imaginary)) {
+ final double im = real == Double.NEGATIVE_INFINITY ? PI_3_OVER_4 : PI_OVER_4;
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(im, imaginary));
}
+ // imaginary is NaN
+ return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
+ }
+ // real is NaN
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
}
- return square().subtract(ONE).sqrt().add(this).log();
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
/**
@@ -1165,25 +1220,36 @@ public final class Complex implements Serializable {
* @return the hyperbolic cosine of this complex number
*/
private static Complex cosh(double real, double imaginary, ComplexConstructor constructor) {
- if (real == 0 &&
- imaginary == Double.POSITIVE_INFINITY) {
- return constructor.create(Double.NaN, 0);
- } else if (real == 0 &&
- Double.isNaN(imaginary)) {
- return constructor.create(Double.NaN, 0);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == 0) {
- return constructor.create(Double.POSITIVE_INFINITY, 0);
- } else if (real == Double.POSITIVE_INFINITY &&
- (imaginary == Double.POSITIVE_INFINITY || Double.isNaN(imaginary))) {
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ return constructor.create(Math.cosh(real) * Math.cos(imaginary),
+ Math.sinh(real) * Math.sin(imaginary));
+ }
+ // Special case for real == 0
+ final double im = real == 0 ? Math.copySign(0, imaginary) : Double.NaN;
+ return constructor.create(Double.NaN, im);
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ if (imaginary == 0) {
+ // Determine sign
+ final double im = real > 0 ? imaginary : -imaginary;
+ return constructor.create(Double.POSITIVE_INFINITY, im);
+ }
+ // inf * cis(y)
+ final double re = real * Math.cos(imaginary);
+ final double im = real * Math.sin(imaginary);
+ return constructor.create(re, im);
+ }
+ // imaginary is infinite or NaN
return constructor.create(Double.POSITIVE_INFINITY, Double.NaN);
- } else if (Double.isNaN(real) &&
- imaginary == 0) {
- return constructor.create(Double.NaN, 0);
}
-
- return constructor.create(Math.cosh(real) * Math.cos(imaginary),
- Math.sinh(real) * Math.sin(imaginary));
+ // real is NaN
+ if (imaginary == 0) {
+ return constructor.create(Double.NaN, Math.copySign(0, imaginary));
+ }
+ // optionally raises the ‘‘invalid’’ floating-point exception, for nonzero y.
+ return NAN;
}
/**
@@ -1201,25 +1267,48 @@ public final class Complex implements Serializable {
* @return <code><i>e</i><sup>this</sup></code>.
*/
public Complex exp() {
- if (real == Double.POSITIVE_INFINITY &&
- imaginary == 0) {
- return new Complex(Double.POSITIVE_INFINITY, 0);
- } else if (real == Double.NEGATIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return Complex.ZERO;
- } else if (real == Double.POSITIVE_INFINITY &&
- (imaginary == Double.POSITIVE_INFINITY || Double.isNaN(imaginary))) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
- } else if (real == Double.NEGATIVE_INFINITY &&
- Double.isNaN(imaginary)) {
- return Complex.ZERO;
- } else if (Double.isNaN(real) &&
- imaginary == 0) {
- return new Complex(Double.NaN, 0);
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ final double expReal = Math.exp(real);
+ if (imaginary == 0) {
+ // Preserve sign for conjugate equality
+ return new Complex(expReal, imaginary);
+ }
+ return new Complex(expReal * Math.cos(imaginary),
+ expReal * Math.sin(imaginary));
+ }
+ // Imaginary is infinite or nan
+ return NAN;
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ if (real == Double.POSITIVE_INFINITY) {
+ if (imaginary == 0) {
+ return this;
+ }
+ // inf * cis(y)
+ final double re = Double.POSITIVE_INFINITY * Math.cos(imaginary);
+ final double im = Double.POSITIVE_INFINITY * Math.sin(imaginary);
+ return new Complex(re, im);
+ }
+ // +0 * cis(y)
+ final double re = 0.0 * Math.cos(imaginary);
+ final double im = 0.0 * Math.sin(imaginary);
+ return new Complex(re, im);
+ }
+ // imaginary is infinite or NaN
+ if (real == Double.POSITIVE_INFINITY) {
+ return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
+ }
+ // Preserve sign for conjugate equality
+ return new Complex(0, Math.copySign(0, imaginary));
+ }
+ // real is NaN
+ if (imaginary == 0) {
+ return new Complex(Double.NaN, Math.copySign(0, imaginary));
}
- final double expReal = Math.exp(real);
- return new Complex(expReal * Math.cos(imaginary),
- expReal * Math.sin(imaginary));
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
/**
@@ -1231,22 +1320,13 @@ public final class Complex implements Serializable {
* log(a + bi) = ln(|a + bi|) + arg(a + bi)i
* </pre>
* where ln on the right hand side is {@link Math#log},
- * {@code |a + bi|} is the modulus, {@link Complex#abs}, and
+ * {@code |a + bi|} is the modulus, {@link Complex#abs}, and
* {@code arg(a + bi) = }{@link Math#atan2}(b, a).
*
- * @return the value <code>ln this</code>, the natural logarithm
- * of {@code this}.
+ * @return the natural logarithm of {@code this}.
*/
public Complex log() {
- if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_4);
- } else if ((real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) ||
- (Double.isNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY)) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
- }
+ // All edge cases satisfied by the Math library
return new Complex(Math.log(abs()),
Math.atan2(imaginary, real));
}
@@ -1255,21 +1335,19 @@ public final class Complex implements Serializable {
* Compute the base 10 or
* <a href="http://mathworld.wolfram.com/CommonLogarithm.html">
* common logarithm</a> of this complex number.
+ * Implements the formula:
+ * <pre>
+ * log10(a + bi) = log(|a + bi|) + arg(a + bi)i
+ * </pre>
+ * where log on the right hand side is {@link Math#log10},
+ * {@code |a + bi|} is the modulus, {@link Complex#abs}, and
+ * {@code arg(a + bi) = }{@link Math#atan2}(b, a).
*
- * @return the base 10 logarithm of <code>this</code>.
+ * @return the base 10 logarithm of {@code this}.
*/
public Complex log10() {
- // Same edge cases as log()
- if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, PI_OVER_4);
- } else if ((real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) ||
- (Double.isNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY)) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
- }
- return new Complex(Math.log(abs()) / Math.log(10),
+ // All edge cases satisfied by the Math library
+ return new Complex(Math.log10(abs()),
Math.atan2(imaginary, real));
}
@@ -1380,33 +1458,34 @@ public final class Complex implements Serializable {
* @return the hyperbolic sine of this complex number
*/
private static Complex sinh(double real, double imaginary, ComplexConstructor constructor) {
- if (real == 0 &&
- imaginary == 0) {
- // Ignore the constructor.
- // It is used in trignomic identities to multiply the result by -i.
- // Here the result would still be zero.
- return Complex.ZERO;
- } else if (real == 0 &&
- imaginary == Double.POSITIVE_INFINITY) {
- return constructor.create(0, Double.NaN);
- } else if (real == 0 &&
- Double.isNaN(imaginary)) {
- return constructor.create(0, Double.NaN);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == 0) {
- return constructor.create(Double.POSITIVE_INFINITY, 0);
- } else if (real == Double.POSITIVE_INFINITY &&
- imaginary == Double.POSITIVE_INFINITY) {
- return constructor.create(Double.POSITIVE_INFINITY, Double.NaN);
- } else if (real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) {
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ return constructor.create(Math.sinh(real) * Math.cos(imaginary),
+ Math.cosh(real) * Math.sin(imaginary));
+ }
+ // Special case for real == 0
+ final double re = real == 0 ? real : Double.NaN;
+ return constructor.create(re, Double.NaN);
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ if (imaginary == 0) {
+ return constructor.create(real, imaginary);
+ }
+ // inf * cis(y)
+ final double re = real * Math.cos(imaginary);
+ final double im = real * Math.sin(imaginary);
+ return constructor.create(re, im);
+ }
+ // imaginary is infinite or NaN
return constructor.create(Double.POSITIVE_INFINITY, Double.NaN);
- } else if (Double.isNaN(real) &&
- imaginary == 0) {
- return constructor.create(Double.NaN, 0);
}
- return constructor.create(Math.sinh(real) * Math.cos(imaginary),
- Math.cosh(real) * Math.sin(imaginary));
+ // real is NaN
+ if (imaginary == 0) {
+ return constructor.create(Double.NaN, Math.copySign(0, imaginary));
+ }
+ // optionally raises the ‘‘invalid’’ floating-point exception, for nonzero y.
+ return NAN;
}
/**
@@ -1427,29 +1506,37 @@ public final class Complex implements Serializable {
* @return the square root of {@code this}.
*/
public Complex sqrt() {
- if (real == 0 &&
- imaginary == 0) {
- return ZERO;
- } else if (neitherInfiniteNorZeroNorNaN(real) &&
- imaginary == Double.POSITIVE_INFINITY) {
- return new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
- } else if (real == Double.NEGATIVE_INFINITY &&
- neitherInfiniteNorZeroNorNaN(imaginary)) {
- return new Complex(0, Double.NaN);
- } else if (real == Double.NEGATIVE_INFINITY &&
- Double.isNaN(imaginary)) {
- return new Complex(Double.NaN, Double.POSITIVE_INFINITY);
- } else if (real == Double.POSITIVE_INFINITY &&
- Double.isNaN(imaginary)) {
- return new Complex(Double.POSITIVE_INFINITY, Double.NaN);
+ // Special case for infinite imaginary for all real including nan
+ if (Double.isInfinite(imaginary)) {
+ return new Complex(Double.POSITIVE_INFINITY, imaginary);
}
-
- final double t = Math.sqrt((Math.abs(real) + abs()) / 2);
- if (real >= 0) {
- return new Complex(t, imaginary / (2 * t));
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ // Handle zero
+ if (real == 0 && imaginary == 0) {
+ return new Complex(0, imaginary);
+ }
+ final double t = Math.sqrt((Math.abs(real) + abs()) / 2);
+ if (real >= 0) {
+ return new Complex(t, imaginary / (2 * t));
+ }
+ return new Complex(Math.abs(imaginary) / (2 * t),
+ Math.copySign(1d, imaginary) * t);
+ }
+ // Imaginary is nan
+ return NAN;
+ }
+ if (Double.isInfinite(real)) {
+ // imaginary is finite or NaN
+ final double part = Double.isNaN(imaginary) ? Double.NaN : 0;
+ if (real == Double.NEGATIVE_INFINITY) {
+ return new Complex(part, Math.copySign(Double.POSITIVE_INFINITY, imaginary));
+ }
+ return new Complex(Double.POSITIVE_INFINITY, Math.copySign(part, imaginary));
}
- return new Complex(Math.abs(imaginary) / (2 * t),
- Math.copySign(1d, imaginary) * t);
+ // real is NaN
+ // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
+ return NAN;
}
/**
@@ -1530,19 +1617,30 @@ public final class Complex implements Serializable {
// Math.cosh returns positive infinity for infinity.
// Math.sinh returns the input infinity for infinity.
- if (real == Double.POSITIVE_INFINITY &&
- (imaginary == Double.POSITIVE_INFINITY || Double.isNaN(imaginary))) {
- return constructor.create(1, 0);
- } else if (Double.isNaN(real) &&
- imaginary == 0) {
- return constructor.create(Double.NaN, 0);
- }
- final double real2 = 2 * real;
- final double imaginary2 = 2 * imaginary;
- final double d = Math.cosh(real2) + Math.cos(imaginary2);
+ if (Double.isFinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ final double real2 = 2 * real;
+ final double imaginary2 = 2 * imaginary;
+ final double d = Math.cosh(real2) + Math.cos(imaginary2);
- return constructor.create(Math.sinh(real2) / d,
- Math.sin(imaginary2) / d);
+ return constructor.create(Math.sinh(real2) / d,
+ Math.sin(imaginary2) / d);
+ }
+ return NAN;
+ }
+ if (Double.isInfinite(real)) {
+ if (Double.isFinite(imaginary)) {
+ return constructor.create(Math.copySign(1, real), Math.copySign(0, Math.sin(2 * imaginary)));
+ }
+ // imaginary is infinite or NaN
+ return constructor.create(Math.copySign(1, real), Math.copySign(0, imaginary));
+ }
+ // real is NaN
+ if (imaginary == 0) {
+ return constructor.create(Double.NaN, Math.copySign(0, imaginary));
+ }
+ // optionally raises the ‘‘invalid’’ floating-point exception, for nonzero y.
+ return NAN;
}
/**
@@ -1647,37 +1745,19 @@ public final class Complex implements Serializable {
}
/**
- * Check that a value meets all the following conditions.
- * <ul>
- * <li>it is not {@code NaN},</li>
- * <li>it is not infinite,</li>
- * <li>it is not zero,</li>
- * </ul>
- *
- * @param d Value.
- * @return {@code true} if {@code d} meets all the conditions and
- * {@code false} otherwise.
- */
- private static boolean neitherInfiniteNorZeroNorNaN(double d) {
- return Double.isFinite(d) && d != 0;
- }
-
- /**
- * Check that a value meets all the following conditions.
+ * Check that a value is negative. It must meet all the following conditions:
* <ul>
* <li>it is not {@code NaN},</li>
- * <li>it is not infinite,</li>
- * <li>it is positive signed,</li>
+ * <li>it is negative signed,</li>
* </ul>
*
- * <p>Note: This is false for negative zero.</p>
+ * <p>Note: This is true for negative zero.</p>
*
* @param d Value.
- * @return {@code true} if {@code d} meets all the conditions and
- * {@code false} otherwise.
+ * @return {@code true} if {@code d} is negative.
*/
- private static boolean positiveSignedFinite(double d) {
- return (Double.isFinite(d) && d > 0) || Double.doubleToLongBits(d) == 0;
+ private static boolean negative(double d) {
+ return d < 0 || equals(d, -0.0);
}
/**
@@ -1697,6 +1777,23 @@ public final class Complex implements Serializable {
return new Complex(imaginary, -real);
}
+ /**
+ * Create the conjugate of a complex number given the real and imaginary parts.
+ * This is used in functions that implement conjugate identities. It is the functional
+ * equivalent of:
+ *
+ * <pre>
+ * z = new Complex(real, imaginary).conjugate();
+ * </pre>
+ *
+ * @param real Real part.
+ * @param imaginary Imaginary part.
+ * @return {@code Complex} object
+ */
+ private static Complex ofCartesianConjugate(double real, double imaginary) {
+ return new Complex(real, -imaginary);
+ }
+
/** See {@link #parse(String)}. */
private static class ComplexParsingException extends NumberFormatException {
/** Serializable version identifier. */
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 269ab74..cddf068 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
@@ -17,7 +17,6 @@
package org.apache.commons.numbers.complex;
-import org.apache.commons.numbers.core.Precision;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.jupiter.api.Assertions;
@@ -26,6 +25,7 @@ import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
/**
* Tests the standards defined by the C.99 standard for complex numbers
@@ -43,15 +43,19 @@ public class CStandardTest {
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 oneOne = complex(1, 1);
private static final Complex oneZero = complex(1, 0);
private static final Complex oneInf = complex(1, inf);
private static final Complex oneNaN = complex(1, nan);
+ private static final Complex oneNegZero = complex(1, -0.0);
private static final Complex zeroInf = complex(0, inf);
private static final Complex zeroNegInf = complex(0, negInf);
+ private static final Complex zeroNegZero = complex(0, -0.0);
private static final Complex zeroNaN = complex(0, nan);
private static final Complex zeroPiTwo = complex(0.0, piOverTwo);
+ private static final Complex negZeroPiTwo = complex(-0.0, piOverTwo);
private static final Complex negZeroZero = complex(-0.0, 0);
+ private static final Complex negZeroNegZero = complex(-0.0, -0.0);
+ private static final Complex negZeroNaN = complex(-0.0, nan);
private static final Complex negI = complex(0.0, -1.0);
private static final Complex infOne = complex(inf, 1);
private static final Complex infZero = complex(inf, 0);
@@ -61,12 +65,16 @@ public class CStandardTest {
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 infNegZero = complex(inf, -0.0);
+ private static final Complex negOneZero = complex(-1, 0);
+ private static final Complex negOneNegZero = complex(-1, -0.0);
private static final Complex negInfInf = complex(negInf, inf);
private static final Complex negInfZero = complex(negInf, 0);
+ private static final Complex negInfNegZero = complex(negInf, -0.0);
private static final Complex negInfOne = complex(negInf, 1);
private static final Complex negInfNaN = complex(negInf, nan);
- private static final Complex negInfPosInf = complex(negInf, inf);
private static final Complex negInfPi = complex(negInf, Math.PI);
+ private static final Complex negInfPiFour = complex(negInf, piOverFour);
private static final Complex nanInf = complex(nan, inf);
private static final Complex nanNegInf = complex(nan, negInf);
private static final Complex nanZero = complex(nan, 0);
@@ -83,33 +91,16 @@ public class CStandardTest {
private static final Complex nanMax = complex(nan, max);
/**
- * Assert the two complex numbers have their real and imaginary components within
- * the given tolerance.
- *
- * @param c1 the first complex
- * @param c2 the second complex
- * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
- * values between the real (resp. imaginary) parts of {@code x} and
- * {@code y}.
- */
- public void assertComplex(Complex c1, Complex c2, int maxUlps) {
- if (!Precision.equals(c1.getReal(), c2.getReal(), maxUlps) ||
- !Precision.equals(c1.getImaginary(), c2.getImaginary(), maxUlps)) {
- Assertions.fail(c1 + " != " + c2);
- }
- }
-
- /**
* Assert the two complex numbers have equivalent real and imaginary components as
* defined by the {@code ==} operator.
*
- * @param c1 the first complex
- * @param c2 the second complex
+ * @param c1 the first complex (actual)
+ * @param c2 the second complex (expected)
*/
- public void assertComplex(Complex c1, Complex c2) {
+ private static void assertComplex(Complex c1, Complex c2) {
// Use a delta of zero to allow comparison of -0.0 to 0.0
- Assertions.assertEquals(c1.getReal(), c2.getReal(), 0.0, "real");
- Assertions.assertEquals(c1.getImaginary(), c2.getImaginary(), 0.0, "imaginary");
+ Assertions.assertEquals(c2.getReal(), c1.getReal(), 0.0, "real");
+ Assertions.assertEquals(c2.getImaginary(), c1.getImaginary(), 0.0, "imaginary");
}
/**
@@ -131,6 +122,109 @@ public class CStandardTest {
}
/**
+ * 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.
+ *
+ * @param operation the operation
+ */
+ private static void assertConjugateEquality(UnaryOperator<Complex> operation) {
+ // Edge cases
+ 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(x, y, operation);
+ }
+ }
+ }
+ // Random numbers
+ final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+ for (int i = 0; i < 100; i++) {
+ final double x = next(rng);
+ final double y = next(rng);
+ assertConjugateEquality(x, y, operation);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param re the real component
+ * @param im the imaginary component
+ * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
+ * values between the real (resp. imaginary) parts of {@code x} and
+ * {@code y}.
+ * @param operation the operation
+ */
+ private static void assertConjugateEquality(double re, double im,
+ UnaryOperator<Complex> operation) {
+ final Complex z = complex(re, im);
+ final Complex c1 = operation.apply(z.conj());
+ final Complex c2 = operation.apply(z).conj();
+
+ // Test for binary equality
+ if (!equals(c1.getReal(), c2.getReal()) ||
+ !equals(c1.getImaginary(), c2.getImaginary())) {
+ Assertions.fail(String.format("Conjugate equality failed (z=%s). Expected: %s but was: %s",
+ z, c2, c1));
+ }
+ }
+
+ /**
+ * 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) {
+ // Test the operation
+ final Complex c1 = operation.apply(z);
+ if (!equals(c1.getReal(), expected.getReal()) ||
+ !equals(c1.getImaginary(), expected.getImaginary())) {
+ Assertions.fail(String.format("Operation failed (z=%s). Expected: %s but was: %s",
+ z, expected, c1));
+ }
+
+ if (!Double.isNaN(z.getImaginary())) {
+ assertConjugateEquality(z.getReal(), z.getImaginary(), operation);
+ }
+ }
+
+ /**
+ * 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
@@ -191,8 +285,8 @@ public class CStandardTest {
*/
private static ArrayList<Complex> createCombinations(final double[] values, Predicate<Complex> condition) {
final ArrayList<Complex> list = new ArrayList<>();
- for (double re : values) {
- for (double im : values) {
+ for (final double re : values) {
+ for (final double im : values) {
final Complex z = complex(re, im);
if (condition.test(z)) {
list.add(z);
@@ -230,18 +324,18 @@ public class CStandardTest {
// "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 (Complex z : infinites) {
- for (Complex w : infinites) {
+ 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 (Complex w : nonZeroFinites) {
+ 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 (Complex w : zeroFinites) {
+ for (final Complex w : zeroFinites) {
assertOperation(z, w, Complex::multiply, "*", Complex::isNaN, "NaN");
assertOperation(w, z, Complex::multiply, "*", Complex::isNaN, "NaN");
}
@@ -258,7 +352,7 @@ public class CStandardTest {
// Check multiply with (NaN,NaN) is not corrected
final double[] values = {0, 1, inf, negInf, nan};
- for (Complex z : createCombinations(values, c -> true)) {
+ for (final Complex z : createCombinations(values, c -> true)) {
assertOperation(z, NAN, Complex::multiply, "*", Complex::isNaN, "NaN");
assertOperation(NAN, z, Complex::multiply, "*", Complex::isNaN, "NaN");
}
@@ -285,34 +379,34 @@ public class CStandardTest {
// "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 (Complex z : infinites) {
- for (Complex w : nonZeroFinites) {
+ for (final Complex z : infinites) {
+ for (final Complex w : nonZeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
- for (Complex w : zeroFinites) {
+ for (final Complex w : zeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
// Check inf/inf cannot be done
- for (Complex w : infinites) {
+ 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 (Complex z : finites) {
- for (Complex w : infinites) {
+ 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 (Complex w : zeroFinites) {
- for (Complex z : nonZeroFinites) {
+ for (final Complex w : zeroFinites) {
+ for (final Complex z : nonZeroFinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
- for (Complex z : infinites) {
+ for (final Complex z : infinites) {
assertOperation(z, w, Complex::divide, "/", Complex::isInfinite, "Inf");
}
}
@@ -320,20 +414,20 @@ public class CStandardTest {
// 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 (Complex w : nans) {
- for (Complex z : finites) {
+ for (final Complex w : nans) {
+ for (final Complex z : finites) {
assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)");
}
- for (Complex z : infinites) {
+ for (final Complex z : infinites) {
assertOperation(z, w, Complex::divide, "/", c -> NAN.equals(c), "(NaN,NaN)");
}
}
// Check (NaN,NaN) divide is not corrected for the edge case of divide by zero or infinite
- for (Complex w : zeroFinites) {
+ for (final Complex w : zeroFinites) {
assertOperation(NAN, w, Complex::divide, "/", Complex::isNaN, "NaN");
}
- for (Complex w : infinites) {
+ for (final Complex w : infinites) {
assertOperation(NAN, w, Complex::divide, "/", Complex::isNaN, "NaN");
}
}
@@ -367,13 +461,13 @@ public class CStandardTest {
}
/**
- * Create a number in the range {@code (-1,1)}.
+ * Create a number in the range {@code (-5,5)}.
*
* @param rng the random generator
* @return the number
*/
private static double next(UniformRandomProvider rng) {
- return rng.nextDouble() * (rng.nextBoolean() ? -1 : 1);
+ return rng.nextDouble() * (rng.nextBoolean() ? -5 : 5);
}
/**
@@ -402,21 +496,23 @@ public class CStandardTest {
*/
@Test
public void testAcos() {
- assertComplex(oneOne.acos().conj(), oneOne.conj().acos(), 1);
- assertComplex(Complex.ZERO.acos(), piTwoNegZero);
- assertComplex(negZeroZero.acos(), piTwoNegZero);
- assertComplex(zeroNaN.acos(), piTwoNaN);
- assertComplex(oneInf.acos(), piTwoNegInf);
- assertComplex(oneNaN.acos(), NAN);
- assertComplex(negInfOne.acos(), piNegInf);
- assertComplex(infOne.acos(), zeroNegInf);
- assertComplex(negInfPosInf.acos(), threePiFourNegInf);
- assertComplex(infInf.acos(), piFourNegInf);
- assertComplex(infNaN.acos(), nanInf);
- assertComplex(negInfNaN.acos(), nanNegInf);
- assertComplex(nanOne.acos(), NAN);
- assertComplex(nanInf.acos(), nanNegInf);
- assertComplex(NAN.acos(), NAN);
+ assertConjugateEquality(Complex::acos);
+ assertComplex(Complex.ZERO, Complex::acos, piTwoNegZero);
+ assertComplex(negZeroZero, Complex::acos, piTwoNegZero);
+ assertComplex(zeroNaN, Complex::acos, piTwoNaN);
+ assertComplex(oneNaN, Complex::acos, NAN);
+ assertComplex(oneInf, Complex::acos, piTwoNegInf);
+ assertComplex(negInfZero, Complex::acos, piNegInf);
+ assertComplex(negInfOne, Complex::acos, piNegInf);
+ assertComplex(infZero, Complex::acos, zeroNegInf);
+ assertComplex(infOne, Complex::acos, zeroNegInf);
+ assertComplex(negInfInf, Complex::acos, threePiFourNegInf);
+ assertComplex(infInf, Complex::acos, piFourNegInf);
+ assertComplex(infNaN, Complex::acos, nanInf);
+ assertComplex(negInfNaN, Complex::acos, nanNegInf);
+ assertComplex(nanOne, Complex::acos, NAN);
+ assertComplex(nanInf, Complex::acos, nanNegInf);
+ assertComplex(NAN, Complex::acos, NAN);
}
/**
@@ -424,51 +520,51 @@ public class CStandardTest {
*/
@Test
public void testAcosh() {
- assertComplex(oneOne.acosh().conj(), oneOne.conj().acosh(), 1);
- assertComplex(Complex.ZERO.acosh(), zeroPiTwo);
- assertComplex(negZeroZero.acosh(), zeroPiTwo);
- assertComplex(oneInf.acosh(), infPiTwo);
- assertComplex(zeroNaN.acosh(), NAN);
- assertComplex(oneNaN.acosh(), NAN);
- assertComplex(negInfOne.acosh(), infPi);
- assertComplex(negInfZero.acosh(), infPi);
- assertComplex(infOne.acosh(), infZero);
- assertComplex(infZero.acosh(), infZero);
- assertComplex(negInfPosInf.acosh(), infThreePiFour);
- assertComplex(infInf.acosh(), infPiFour);
- assertComplex(infNaN.acosh(), infNaN);
- assertComplex(negInfNaN.acosh(), infNaN);
- assertComplex(nanOne.acosh(), NAN);
- assertComplex(nanInf.acosh(), infNaN);
- assertComplex(NAN.acosh(), NAN);
- // The standard mentions positive-signed y
- Assertions.assertThrows(AssertionError.class, () -> {
- // Not −∞ + iy, positive signed y
- assertComplex(complex(negInf, -0.0).acosh(), infPi);
- });
- Assertions.assertThrows(AssertionError.class, () -> {
- // Not ∞ + iy, positive signed y
- assertComplex(complex(inf, -0.0).acosh(), infPi);
- });
+ assertConjugateEquality(Complex::acosh);
+ assertComplex(Complex.ZERO, Complex::acosh, zeroPiTwo);
+ assertComplex(negZeroZero, Complex::acosh, negZeroPiTwo);
+ assertComplex(zeroNaN, Complex::acosh, NAN);
+ assertComplex(oneNaN, Complex::acosh, NAN);
+ assertComplex(oneInf, Complex::acosh, infPiTwo);
+ assertComplex(negInfZero, Complex::acosh, infPi);
+ assertComplex(negInfOne, Complex::acosh, infPi);
+ assertComplex(infZero, Complex::acosh, infZero);
+ assertComplex(infOne, Complex::acosh, infZero);
+ assertComplex(negInfInf, Complex::acosh, infThreePiFour);
+ assertComplex(infInf, Complex::acosh, infPiFour);
+ assertComplex(infNaN, Complex::acosh, infNaN);
+ assertComplex(negInfNaN, Complex::acosh, infNaN);
+ assertComplex(nanOne, Complex::acosh, NAN);
+ assertComplex(nanInf, Complex::acosh, infNaN);
+ assertComplex(NAN, Complex::acosh, NAN);
}
+ // TODO: test the 'IS ODD/ EVEN' specification
+
/**
* ISO C Standard G.6.2.2.
*/
@Test
public void testAsinh() {
- // TODO: test for which Asinh is odd
- assertComplex(oneOne.conj().asinh(), oneOne.asinh().conj());
- assertComplex(Complex.ZERO.asinh(), Complex.ZERO);
- assertComplex(oneInf.asinh(), infPiTwo);
- assertComplex(oneNaN.asinh(), NAN);
- assertComplex(infOne.asinh(), infZero);
- assertComplex(infInf.asinh(), infPiFour);
- assertComplex(infNaN.asinh(), infNaN);
- assertComplex(nanZero.asinh(), nanZero);
- assertComplex(nanOne.asinh(), NAN);
- assertComplex(nanInf.asinh(), infNaN);
- assertComplex(NAN, NAN);
+ assertConjugateEquality(Complex::asinh);
+ // AND ASINH IS ODD
+ assertComplex(Complex.ZERO, Complex::asinh, Complex.ZERO);
+ assertComplex(negZeroZero, Complex::asinh, negZeroZero);
+ assertComplex(zeroNaN, Complex::asinh, NAN);
+ assertComplex(oneNaN, Complex::asinh, NAN);
+ assertComplex(oneInf, Complex::asinh, infPiTwo);
+ assertComplex(negInfZero, Complex::asinh, negInfZero);
+ assertComplex(negInfOne, Complex::asinh, negInfZero);
+ assertComplex(infZero, Complex::asinh, infZero);
+ assertComplex(infOne, Complex::asinh, infZero);
+ assertComplex(negInfInf, Complex::asinh, negInfPiFour);
+ assertComplex(infInf, Complex::asinh, infPiFour);
+ assertComplex(infNaN, Complex::asinh, infNaN);
+ assertComplex(negInfNaN, Complex::asinh, negInfNaN);
+ assertComplex(nanZero, Complex::asinh, nanZero);
+ assertComplex(nanOne, Complex::asinh, NAN);
+ assertComplex(nanInf, Complex::asinh, infNaN);
+ assertComplex(NAN, Complex::asinh, NAN);
}
/**
@@ -476,18 +572,26 @@ public class CStandardTest {
*/
@Test
public void testAtanh() {
- assertComplex(oneOne.conj().atanh(), oneOne.atanh().conj());
- assertComplex(Complex.ZERO.atanh(), Complex.ZERO);
- assertComplex(zeroNaN.atanh(), zeroNaN);
- assertComplex(oneZero.atanh(), infZero);
- assertComplex(oneInf.atanh(), zeroPiTwo);
- assertComplex(oneNaN.atanh(), NAN);
- assertComplex(infOne.atanh(), zeroPiTwo);
- assertComplex(infInf.atanh(), zeroPiTwo);
- assertComplex(infNaN.atanh(), zeroNaN);
- assertComplex(nanOne.atanh(), NAN);
- assertComplex(nanInf.atanh(), zeroPiTwo);
- assertComplex(NAN.atanh(), NAN);
+ assertConjugateEquality(Complex::atanh);
+ // AND ATANH IS ODD
+ assertComplex(Complex.ZERO, Complex::atanh, Complex.ZERO);
+ assertComplex(negZeroZero, Complex::atanh, negZeroZero);
+ assertComplex(zeroNaN, Complex::atanh, zeroNaN);
+ assertComplex(oneNaN, Complex::atanh, NAN);
+ assertComplex(oneZero, Complex::atanh, infZero);
+ assertComplex(oneInf, Complex::atanh, zeroPiTwo);
+ assertComplex(negInfZero, Complex::atanh, negZeroPiTwo);
+ assertComplex(negInfOne, Complex::atanh, negZeroPiTwo);
+ assertComplex(infZero, Complex::atanh, zeroPiTwo);
+ assertComplex(infOne, Complex::atanh, zeroPiTwo);
+ assertComplex(negInfInf, Complex::atanh, negZeroPiTwo);
+ assertComplex(infInf, Complex::atanh, zeroPiTwo);
+ assertComplex(infNaN, Complex::atanh, zeroNaN);
+ assertComplex(negInfNaN, Complex::atanh, negZeroNaN);
+ assertComplex(nanZero, Complex::atanh, NAN);
+ assertComplex(nanOne, Complex::atanh, NAN);
+ assertComplex(nanInf, Complex::atanh, zeroPiTwo);
+ assertComplex(NAN, Complex::atanh, NAN);
}
/**
@@ -495,21 +599,38 @@ public class CStandardTest {
*/
@Test
public void testCosh() {
- assertComplex(oneOne.cosh().conj(), oneOne.conj().cosh());
- assertComplex(Complex.ZERO.cosh(), Complex.ONE);
- assertComplex(zeroInf.cosh(), nanZero);
- assertComplex(zeroNaN.cosh(), nanZero);
- assertComplex(oneInf.cosh(), NAN);
- assertComplex(oneNaN.cosh(), NAN);
- assertComplex(infZero.cosh(), infZero);
- // the next test does not appear to make sense:
- // (inf + iy) = inf + cis(y)
- // skipped
- assertComplex(infInf.cosh(), infNaN);
- assertComplex(infNaN.cosh(), infNaN);
- assertComplex(nanZero.cosh(), nanZero);
- assertComplex(nanOne.cosh(), NAN);
- assertComplex(NAN.cosh(), NAN);
+ assertConjugateEquality(Complex::cosh);
+ // AND CCOSH IS EVEN
+ assertComplex(Complex.ZERO, Complex::cosh, Complex.ONE);
+ assertComplex(negZeroZero, Complex::cosh, oneNegZero);
+ assertComplex(zeroInf, Complex::cosh, nanZero);
+ assertComplex(oneInf, Complex::cosh, NAN);
+ assertComplex(zeroNaN, Complex::cosh, nanZero);
+ assertComplex(oneNaN, Complex::cosh, NAN);
+ // (inf + iy) = inf * cis(y)
+ // where cis(y) = cos(y) + i sin(y), and y is finite non-zero
+ //
+ // Note that y == 0: complex(1, 0).multiply(inf) = (inf, NaN)
+ // But the cosh is (inf, 0). This result is computed by g++ and we test it separately.
+ assertComplex(negInfNegZero, Complex::cosh, infZero);
+ assertComplex(negInfZero, Complex::cosh, infNegZero);
+ assertComplex(infNegZero, Complex::cosh, infNegZero);
+ assertComplex(infZero, Complex::cosh, infZero);
+ for (int i = 1; i < 10; i++) {
+ final double y = i * 0.5;
+ assertComplex(complex(inf, y), Complex::cosh, Complex.ofCis(y).multiply(inf));
+ assertComplex(complex(inf, -y), Complex::cosh, Complex.ofCis(-y).multiply(inf));
+ assertComplex(complex(-inf, y), Complex::cosh, Complex.ofCis(y).multiply(-inf));
+ assertComplex(complex(-inf, -y), Complex::cosh, Complex.ofCis(-y).multiply(-inf));
+ }
+ assertComplex(negInfInf, Complex::cosh, infNaN);
+ assertComplex(infInf, Complex::cosh, infNaN);
+ assertComplex(infNaN, Complex::cosh, infNaN);
+ assertComplex(negInfNaN, Complex::cosh, infNaN);
+ assertComplex(nanZero, Complex::cosh, nanZero);
+ assertComplex(nanOne, Complex::cosh, NAN);
+ assertComplex(nanInf, Complex::cosh, NAN);
+ assertComplex(NAN, Complex::cosh, NAN);
}
/**
@@ -517,19 +638,38 @@ public class CStandardTest {
*/
@Test
public void testSinh() {
- assertComplex(oneOne.sinh().conj(), oneOne.conj().sinh()); // AND CSINH IS ODD
- assertComplex(Complex.ZERO.sinh(), Complex.ZERO);
- assertComplex(zeroInf.sinh(), zeroNaN);
- assertComplex(zeroNaN.sinh(), zeroNaN);
- assertComplex(oneInf.sinh(), NAN);
- assertComplex(oneNaN.sinh(), NAN);
- assertComplex(infZero.sinh(), infZero);
- // skipped test similar to previous section
- assertComplex(infInf.sinh(), infNaN);
- assertComplex(infNaN.sinh(), infNaN);
- assertComplex(nanZero.sinh(), nanZero);
- assertComplex(nanOne.sinh(), NAN);
- assertComplex(NAN.sinh(), NAN);
+ assertConjugateEquality(Complex::sinh);
+ // AND CSINH IS ODD
+ assertComplex(Complex.ZERO, Complex::sinh, Complex.ZERO);
+ assertComplex(negZeroZero, Complex::sinh, negZeroZero);
+ assertComplex(zeroInf, Complex::sinh, zeroNaN);
+ assertComplex(oneInf, Complex::sinh, NAN);
+ assertComplex(zeroNaN, Complex::sinh, zeroNaN);
+ assertComplex(oneNaN, Complex::sinh, NAN);
+ // (inf + iy) = inf * cis(y)
+ // where cis(y) = cos(y) + i sin(y), and y is finite non-zero
+ //
+ // Note that y == 0: complex(1, 0).multiply(inf) = (inf, NaN)
+ // But the sinh is (inf, 0). This result is computed by g++ and we test it separately.
+ assertComplex(negInfNegZero, Complex::sinh, negInfNegZero);
+ assertComplex(negInfZero, Complex::sinh, negInfZero);
+ assertComplex(infNegZero, Complex::sinh, infNegZero);
+ assertComplex(infZero, Complex::sinh, infZero);
+ for (int i = 1; i < 10; i++) {
+ final double y = i * 0.5;
+ assertComplex(complex(inf, y), Complex::sinh, Complex.ofCis(y).multiply(inf));
+ assertComplex(complex(inf, -y), Complex::sinh, Complex.ofCis(-y).multiply(inf));
+ assertComplex(complex(-inf, y), Complex::sinh, Complex.ofCis(y).multiply(-inf));
+ assertComplex(complex(-inf, -y), Complex::sinh, Complex.ofCis(-y).multiply(-inf));
+ }
+ assertComplex(negInfInf, Complex::sinh, infNaN);
+ assertComplex(infInf, Complex::sinh, infNaN);
+ assertComplex(infNaN, Complex::sinh, infNaN);
+ assertComplex(negInfNaN, Complex::sinh, infNaN);
+ assertComplex(nanZero, Complex::sinh, nanZero);
+ assertComplex(nanOne, Complex::sinh, NAN);
+ assertComplex(nanInf, Complex::sinh, NAN);
+ assertComplex(NAN, Complex::sinh, NAN);
}
/**
@@ -537,16 +677,36 @@ public class CStandardTest {
*/
@Test
public void testTanh() {
- assertComplex(oneOne.tanh().conj(), oneOne.conj().tanh()); // AND CSINH IS ODD
- assertComplex(Complex.ZERO.tanh(), Complex.ZERO);
- assertComplex(oneInf.tanh(), NAN);
- assertComplex(oneNaN.tanh(), NAN);
- //Do Not Understand the Next Test
- assertComplex(infInf.tanh(), oneZero);
- assertComplex(infNaN.tanh(), oneZero);
- assertComplex(nanZero.tanh(), nanZero);
- assertComplex(nanOne.tanh(), NAN);
- assertComplex(NAN.tanh(), NAN);
+ assertConjugateEquality(Complex::tanh);
+ // AND TANH IS ODD
+ assertComplex(Complex.ZERO, Complex::tanh, Complex.ZERO);
+ assertComplex(negZeroZero, Complex::tanh, negZeroZero);
+ assertComplex(zeroInf, Complex::tanh, NAN);
+ assertComplex(oneInf, Complex::tanh, NAN);
+ assertComplex(zeroNaN, Complex::tanh, NAN);
+ assertComplex(oneNaN, Complex::tanh, NAN);
+ // (inf + iy) = 1 + i0 sin(2y), and y is positive-signed finite
+ // Note: no specification for other -inf and/or negative y.
+ // g++ returns the result using (+/-1, i0 sin(2y)) where the sign of the 1 is from the inf.
+ assertComplex(negInfNegZero, Complex::tanh, negOneNegZero);
+ assertComplex(negInfZero, Complex::tanh, negOneZero);
+ assertComplex(infNegZero, Complex::tanh, oneNegZero);
+ assertComplex(infZero, Complex::tanh, oneZero);
+ for (int i = 1; i < 10; i++) {
+ final double y = i * 0.5;
+ assertComplex(complex(inf, y), Complex::tanh, complex(1.0, Math.copySign(0, Math.sin(2 * y))));
+ assertComplex(complex(inf, -y), Complex::tanh, complex(1.0, Math.copySign(0, Math.sin(2 * -y))));
+ assertComplex(complex(-inf, y), Complex::tanh, complex(-1.0, Math.copySign(0, Math.sin(2 * y))));
+ assertComplex(complex(-inf, -y), Complex::tanh, complex(-1.0, Math.copySign(0, Math.sin(2 * -y))));
+ }
+ assertComplex(negInfInf, Complex::tanh, negOneZero);
+ assertComplex(infInf, Complex::tanh, oneZero);
+ assertComplex(infNaN, Complex::tanh, oneZero);
+ assertComplex(negInfNaN, Complex::tanh, negOneZero);
+ assertComplex(nanZero, Complex::tanh, nanZero);
+ assertComplex(nanOne, Complex::tanh, NAN);
+ assertComplex(nanInf, Complex::tanh, NAN);
+ assertComplex(NAN, Complex::tanh, NAN);
}
/**
@@ -554,20 +714,37 @@ public class CStandardTest {
*/
@Test
public void testExp() {
- assertComplex(oneOne.conj().exp(), oneOne.exp().conj());
- assertComplex(Complex.ZERO.exp(), oneZero);
- assertComplex(negZeroZero.exp(), oneZero);
- assertComplex(oneInf.exp(), NAN);
- assertComplex(oneNaN.exp(), NAN);
- assertComplex(infZero.exp(), infZero);
- // Do not understand next test
- assertComplex(negInfInf.exp(), Complex.ZERO);
- assertComplex(infInf.exp(), infNaN);
- assertComplex(negInfNaN.exp(), Complex.ZERO);
- assertComplex(infNaN.exp(), infNaN);
- assertComplex(nanZero.exp(), nanZero);
- assertComplex(nanOne.exp(), NAN);
- assertComplex(NAN.exp(), NAN);
+ assertConjugateEquality(Complex::exp);
+ assertComplex(Complex.ZERO, Complex::exp, oneZero);
+ assertComplex(negZeroZero, Complex::exp, oneZero);
+ assertComplex(zeroInf, Complex::exp, NAN);
+ assertComplex(oneInf, Complex::exp, NAN);
+ assertComplex(zeroNaN, Complex::exp, NAN);
+ assertComplex(oneNaN, Complex::exp, NAN);
+ assertComplex(infNegZero, Complex::exp, infNegZero);
+ assertComplex(infZero, Complex::exp, infZero);
+ // (-inf + iy) = +0 cis(y)
+ // where cis(y) = cos(y) + i sin(y), and y is finite
+ for (int i = 0; i < 10; i++) {
+ final double y = i * 0.5;
+ assertComplex(complex(-inf, y), Complex::exp, Complex.ofCis(y).multiply(0.0));
+ assertComplex(complex(-inf, -y), Complex::exp, Complex.ofCis(-y).multiply(0.0));
+ }
+ // (inf + iy) = +inf cis(y)
+ // where cis(y) = cos(y) + i sin(y), and y is non-zero finite
+ for (int i = 1; i < 10; i++) {
+ final double y = i * 0.5;
+ assertComplex(complex(inf, y), Complex::exp, Complex.ofCis(y).multiply(inf));
+ assertComplex(complex(inf, -y), Complex::exp, Complex.ofCis(-y).multiply(inf));
+ }
+ assertComplex(negInfInf, Complex::exp, Complex.ZERO);
+ assertComplex(infInf, Complex::exp, infNaN);
+ assertComplex(negInfNaN, Complex::exp, Complex.ZERO);
+ assertComplex(infNaN, Complex::exp, infNaN);
+ assertComplex(nanZero, Complex::exp, nanZero);
+ assertComplex(nanOne, Complex::exp, NAN);
+ assertComplex(nanInf, Complex::exp, NAN);
+ assertComplex(NAN, Complex::exp, NAN);
}
/**
@@ -575,38 +752,52 @@ public class CStandardTest {
*/
@Test
public void testLog() {
- assertComplex(oneOne.log().conj(), oneOne.conj().log());
- assertComplex(negZeroZero.log(), negInfPi);
- assertComplex(Complex.ZERO.log(), negInfZero);
- assertComplex(oneInf.log(), infPiTwo);
- assertComplex(oneNaN.log(), NAN);
- assertComplex(negInfOne.log(), infPi);
- assertComplex(infOne.log(), infZero);
- assertComplex(infInf.log(), infPiFour);
- assertComplex(infNaN.log(), infNaN);
- assertComplex(nanOne.log(), NAN);
- assertComplex(nanInf.log(), infNaN);
- assertComplex(NAN.log(), NAN);
+ assertConjugateEquality(Complex::log);
+ assertComplex(negZeroZero, Complex::log, negInfPi);
+ assertComplex(Complex.ZERO, Complex::log, negInfZero);
+ assertComplex(zeroInf, Complex::log, infPiTwo);
+ assertComplex(oneInf, Complex::log, infPiTwo);
+ assertComplex(zeroNaN, Complex::log, NAN);
+ assertComplex(oneNaN, Complex::log, NAN);
+ assertComplex(negInfZero, Complex::log, infPi);
+ assertComplex(negInfOne, Complex::log, infPi);
+ assertComplex(infZero, Complex::log, infZero);
+ assertComplex(infOne, Complex::log, infZero);
+ assertComplex(negInfInf, Complex::log, infThreePiFour);
+ assertComplex(infInf, Complex::log, infPiFour);
+ assertComplex(negInfNaN, Complex::log, infNaN);
+ assertComplex(infNaN, Complex::log, infNaN);
+ assertComplex(nanZero, Complex::log, NAN);
+ assertComplex(nanOne, Complex::log, NAN);
+ assertComplex(nanInf, Complex::log, infNaN);
+ assertComplex(NAN, Complex::log, NAN);
}
/**
- * Same edge cases as log() since the real component is divided by Math.log(10) whic
+ * Same edge cases as log() since the real component is divided by Math.log(10) which
* has no effect on infinite or nan.
*/
@Test
public void testLog10() {
- assertComplex(oneOne.log10().conj(), oneOne.conj().log10());
- assertComplex(negZeroZero.log10(), negInfPi);
- assertComplex(Complex.ZERO.log10(), negInfZero);
- assertComplex(oneInf.log10(), infPiTwo);
- assertComplex(oneNaN.log10(), NAN);
- assertComplex(negInfOne.log10(), infPi);
- assertComplex(infOne.log10(), infZero);
- assertComplex(infInf.log10(), infPiFour);
- assertComplex(infNaN.log10(), infNaN);
- assertComplex(nanOne.log10(), NAN);
- assertComplex(nanInf.log10(), infNaN);
- assertComplex(NAN.log10(), NAN);
+ assertConjugateEquality(Complex::log10);
+ assertComplex(negZeroZero, Complex::log10, negInfPi);
+ assertComplex(Complex.ZERO, Complex::log10, negInfZero);
+ assertComplex(zeroInf, Complex::log10, infPiTwo);
+ assertComplex(oneInf, Complex::log10, infPiTwo);
+ assertComplex(zeroNaN, Complex::log10, NAN);
+ assertComplex(oneNaN, Complex::log10, NAN);
+ assertComplex(negInfZero, Complex::log10, infPi);
+ assertComplex(negInfOne, Complex::log10, infPi);
+ assertComplex(infZero, Complex::log10, infZero);
+ assertComplex(infOne, Complex::log10, infZero);
+ assertComplex(negInfInf, Complex::log10, infThreePiFour);
+ assertComplex(infInf, Complex::log10, infPiFour);
+ assertComplex(negInfNaN, Complex::log10, infNaN);
+ assertComplex(infNaN, Complex::log10, infNaN);
+ assertComplex(nanZero, Complex::log10, NAN);
+ assertComplex(nanOne, Complex::log10, NAN);
+ assertComplex(nanInf, Complex::log10, infNaN);
+ assertComplex(NAN, Complex::log10, NAN);
}
/**
@@ -614,14 +805,25 @@ public class CStandardTest {
*/
@Test
public void testSqrt2() {
- assertComplex(oneOne.sqrt().conj(), oneOne.conj().sqrt());
- assertComplex(Complex.ZERO.sqrt(), Complex.ZERO);
- assertComplex(oneInf.sqrt(), infInf);
- assertComplex(negInfOne.sqrt(), zeroNaN);
- assertComplex(infOne.sqrt(), infZero);
- assertComplex(negInfNaN.sqrt(), nanInf);
- assertComplex(infNaN.sqrt(), infNaN);
- assertComplex(nanOne.sqrt(), NAN);
- assertComplex(NAN.sqrt(), NAN);
+ assertConjugateEquality(Complex::sqrt);
+ assertComplex(negZeroZero, Complex::sqrt, Complex.ZERO);
+ assertComplex(Complex.ZERO, Complex::sqrt, Complex.ZERO);
+ assertComplex(zeroNegZero, Complex::sqrt, zeroNegZero);
+ assertComplex(negZeroNegZero, Complex::sqrt, zeroNegZero);
+ assertComplex(zeroInf, Complex::sqrt, infInf);
+ assertComplex(oneInf, Complex::sqrt, infInf);
+ assertComplex(infInf, Complex::sqrt, infInf);
+ assertComplex(nanInf, Complex::sqrt, infInf);
+ assertComplex(zeroNaN, Complex::sqrt, NAN);
+ assertComplex(oneNaN, Complex::sqrt, NAN);
+ assertComplex(negInfZero, Complex::sqrt, zeroInf);
+ assertComplex(negInfOne, Complex::sqrt, zeroInf);
+ assertComplex(infZero, Complex::sqrt, infZero);
+ assertComplex(infOne, Complex::sqrt, infZero);
+ assertComplex(negInfNaN, Complex::sqrt, nanInf);
+ assertComplex(infNaN, Complex::sqrt, infNaN);
+ assertComplex(nanZero, Complex::sqrt, NAN);
+ assertComplex(nanOne, Complex::sqrt, NAN);
+ assertComplex(NAN, Complex::sqrt, NAN);
}
}