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/20 17:58:15 UTC

[commons-numbers] 24/30: Fix overflow in tanh. Added edge case tests.

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 <ah...@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