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 3366f35d8120ddc5c52f991c1d120a7c7f95c72f Author: aherbert <aherb...@apache.org> AuthorDate: Fri Dec 20 12:53:06 2019 +0000 Fix overflow in tanh. Added edge case tests. Overflow is cause by computation of Math.sin(2a) or Math.cos(2a) when a is finite and 2a is infinite. This has been fixed to use trigonomic identities for sin(2a) and cos(2a) in this case. --- .../apache/commons/numbers/complex/Complex.java | 69 ++++++++++++++++++---- .../numbers/complex/ComplexEdgeCaseTest.java | 30 +++++++++- 2 files changed, 86 insertions(+), 13 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 c9be094..4516be4 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 @@ -2400,6 +2400,7 @@ public final class Complex implements Serializable { * </pre> * * @return the tangent of {@code this}. + * @see <a href="http://functions.wolfram.com/ElementaryFunctions/Tan/">Tangent</a> */ public Complex tan() { // Define in terms of tanh @@ -2420,6 +2421,7 @@ public final class Complex implements Serializable { * <p>This is an odd function: {@code f(z) = -f(-z)}. * * @return the hyperbolic tangent of {@code this}. + * @see <a href="http://functions.wolfram.com/ElementaryFunctions/Tanh/">Tanh</a> */ public Complex tanh() { return tanh(real, imaginary, Complex::ofCartesian); @@ -2441,10 +2443,8 @@ public final class Complex implements Serializable { // Perform edge-condition checks on twice the imaginary value. // This handles very big imaginary numbers as infinite. - final double imaginary2 = 2 * imaginary; - if (Double.isFinite(real)) { - if (Double.isFinite(imaginary2)) { + if (Double.isFinite(imaginary)) { if (real == 0) { // Identity: sin x / (1 + cos x) = tan(x/2) return constructor.create(real, Math.tan(imaginary)); @@ -2458,26 +2458,26 @@ public final class Complex implements Serializable { // Math.cosh returns positive infinity for infinity. // cosh -> inf - final double d = Math.cosh(real2) + Math.cos(imaginary2); + final double divisor = Math.cosh(real2) + cos2(imaginary); // Math.sinh returns the input infinity for infinity. // sinh -> inf for positive x; else -inf final double sinhRe2 = Math.sinh(real2); // Avoid inf / inf - if (Double.isInfinite(sinhRe2) && Double.isInfinite(d)) { - // Fall-through to the result if infinite - return constructor.create(Math.copySign(1, real), Math.copySign(0, Math.sin(imaginary2))); + if (Double.isInfinite(sinhRe2) && Double.isInfinite(divisor)) { + // Handle as if real was infinite + return constructor.create(Math.copySign(1, real), Math.copySign(0, sin2(imaginary))); } - return constructor.create(sinhRe2 / d, - Math.sin(imaginary2) / d); + return constructor.create(sinhRe2 / divisor, + sin2(imaginary) / divisor); } // imaginary is infinite or NaN return NAN; } if (Double.isInfinite(real)) { - if (Double.isFinite(imaginary2)) { - return constructor.create(Math.copySign(1, real), Math.copySign(0, Math.sin(imaginary2))); + if (Double.isFinite(imaginary)) { + return constructor.create(Math.copySign(1, real), Math.copySign(0, sin2(imaginary))); } // imaginary is infinite or NaN return constructor.create(Math.copySign(1, real), Math.copySign(0, imaginary)); @@ -2490,7 +2490,52 @@ public final class Complex implements Serializable { return NAN; } - /** + /** + * Safely compute {@code cos(2*a)} when {@code a} is finite. + * Note that {@link Math#cos(double)} returns NaN when the input is infinite. + * If {@code 2*a} is finite use {@code Math.cos(2*a)}; otherwise use the identity: + * <pre> + * <code> + * cos(2a) = 2 cos<sup>2</sup>(a) - 1 + * </code> + * </pre> + * + * @param a Angle a. + * @return the cosine of 2a + * @see Math#cos(double) + */ + private static double cos2(double a) { + final double twoA = 2 * a; + if (Double.isFinite(twoA)) { + return Math.cos(twoA); + } + final double cosA = Math.cos(a); + return 2 * cosA * cosA - 1; + } + + /** + * Safely compute {@code sin(2*a)} when {@code a} is finite. + * Note that {@link Math#sin(double)} returns NaN when the input is infinite. + * If {@code 2*a} is finite use {@code Math.sin(2*a)}; otherwise use the identity: + * <pre> + * <code> + * sin(2a) = 2 sin(a) cos(a) + * </code> + * </pre> + * + * @param a Angle a. + * @return the sine of 2a + * @see Math#sin(double) + */ + private static double sin2(double a) { + final double twoA = 2 * a; + if (Double.isFinite(twoA)) { + return Math.sin(twoA); + } + return 2 * Math.sin(a) * Math.cos(a); + } + + /** * Compute the argument of this complex number. * * <p>The argument is the angle phi between the positive real axis and 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 fd163e8..0fb94af 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 @@ -243,7 +243,35 @@ public class ComplexEdgeCaseTest { public void testTanh() { // tan(a + b i) = sinh(2a)/(cosh(2a)+cos(2b)) + i [sin(2b)/(cosh(2a)+cos(2b))] // Odd function: negative real cases defined by positive real cases - // TODO + final String name = "tanh"; + final UnaryOperator<Complex> operation = Complex::tanh; + + // Overflow on 2b: + // cos(2b) = cos(inf) = NaN + // sin(2b) = sin(inf) = NaN + assertComplex(1, Double.MAX_VALUE, name, operation, 0.76160203106265523, -0.0020838895895863505); + + // Underflow on 2b: + // cos(2b) -> 1 + // sin(2b) -> 0 + assertComplex(1, Double.MIN_NORMAL, name, operation, 0.76159415595576485, 9.344739287691424e-309); + assertComplex(1, Double.MIN_VALUE, name, operation, 0.76159415595576485, 0); + + // Overflow on 2a: + // sinh(2a) = sinh(inf) = inf + // cosh(2a) = cosh(inf) = inf + // Test all sign variants as this execution path to treat real as infinite + // is not tested else where. + assertComplex(Double.MAX_VALUE, 1, name, operation, 1, 0.0); + assertComplex(Double.MAX_VALUE, -1, name, operation, 1, -0.0); + assertComplex(-Double.MAX_VALUE, 1, name, operation, -1, 0.0); + assertComplex(-Double.MAX_VALUE, -1, name, operation, -1, -0.0); + + // Underflow on 2a: + // sinh(2a) -> 0 + // cosh(2a) -> 0 + assertComplex(Double.MIN_NORMAL, 1, name, operation, 7.6220323800193346e-308, 1.5574077246549021); + assertComplex(Double.MIN_VALUE, 1, name, operation, 1.4821969375237396e-323, 1.5574077246549021); } @Test