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:57:53 UTC

[commons-numbers] 02/30: Updated cosh/acosh using an overflow/underflow safe method.

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 96fe378a62aa7c5151c35721d05676c27b5164d6
Author: aherbert <ah...@apache.org>
AuthorDate: Thu Dec 19 11:30:35 2019 +0000

    Updated cosh/acosh using an overflow/underflow safe method.
---
 .../apache/commons/numbers/complex/Complex.java    | 209 +++++++++++++++------
 1 file changed, 148 insertions(+), 61 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 feb777c..010b40b 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
@@ -78,9 +78,14 @@ public final class Complex implements Serializable  {
     /** Base 10 logarithm of 2 (log10(2)). */
     private static final double LOG10_2 = Math.log10(2);
 
-    /** Crossover point to switch computation for asin factor A. */
+    /**
+     * Crossover point to switch computation for asin/acos factor A.
+     * This has been updated from the 1.5 value used by Hull et al to 10
+     * as used in boost::math::complex.
+     * @see <a href="https://svn.boost.org/trac/boost/ticket/7290">Boost ticket 7290</a>
+     */
     private static final double A_CROSSOVER = 10;
-    /** Crossover point to switch computation for asin factor B. */
+    /** Crossover point to switch computation for asin/acos factor B. */
     private static final double B_CROSSOVER = 0.6471;
     /**
      * The safe maximum double value {@code x} to avoid loss of precision in asin.
@@ -1261,6 +1266,27 @@ public final class Complex implements Serializable  {
      * </code>
      * </pre>
      *
+     * <p>This is implemented using real {@code x} and imaginary {@code y} parts:</p>
+     * <pre>
+     * <code>
+     *   acos(z) = acos(B) - i ln(A + sqrt(A<sup>2</sup>-1))
+     *   A = 0.5 [ sqrt((x+1)<sup>2</sup>+y<sup>2</sup>) + sqrt((x-1)<sup>2</sup>+y<sup>2</sup>) ]
+     *   B = 0.5 [ sqrt((x+1)<sup>2</sup>+y<sup>2</sup>) - sqrt((x-1)<sup>2</sup>+y<sup>2</sup>) ]
+     * </code>
+     * </pre>
+     *
+     * <p>The implementation is based on the method described in:</p>
+     * <blockquote>
+     * T E Hull, Thomas F Fairgrieve and Ping Tak Peter Tang (1997)
+     * Implementing the complex Arcsine and Arccosine Functions using Exception Handling.
+     * ACM Transactions on Mathematical Software, Vol 23, No 3, pp 299-335.
+     * </blockquote>
+     *
+     * <p>The code has been adapted from the <a href="https://www.boost.org/">Boost</a>
+     * {@code c++} implementation {@code <boost/math/complex/acos.hpp>}. The function is well
+     * defined over the entire complex number range, and produces accurate values even at the
+     * extremes due to special handling of overflow and underflow conditions.</p>
+     *
      * @return the inverse cosine of this complex number.
      */
     public Complex acos() {
@@ -1278,58 +1304,120 @@ public final class Complex implements Serializable  {
      * @param constructor Constructor.
      * @return the inverse cosine of the complex number.
      */
-    private static Complex acos(double real, double imaginary, ComplexConstructor constructor) {
-        if (Double.isFinite(real)) {
-            if (Double.isFinite(imaginary)) {
-                // Special case for real numbers
-                if (imaginary == 0 && Math.abs(real) <= 1) {
-                    return constructor.create(real == 0.0 ? PI_OVER_2 : Math.acos(real),
-                                              Math.copySign(0, -imaginary));
-                }
-                // ISO C99: Preserve the equality
-                // acos(conj(z)) = conj(acos(z))
-                // by always computing on a positive imaginary Complex number.
-                final double a = real;
-                final double b = Math.abs(imaginary);
-                final Complex z2 = multiply(a, b, a, b);
-                // sqrt(1 - z^2)
-                final Complex sqrt1mz2 = sqrt(1 - z2.real, -z2.imaginary);
-                // Compute the rest inline to avoid Complex object creation.
-                // (x + y i) = iz + sqrt(1 - z^2)
-                final double x = -b + sqrt1mz2.real;
-                final double y = a + sqrt1mz2.imaginary;
-                // (re + im i) = (pi / 2) + i ln(iz + sqrt(1 - z^2))
-                final double re = PI_OVER_2 - getArgument(x, y);
-                final double im = Math.log(getAbsolute(x, y));
-                // Map back to the correct sign
-                return constructor.create(re, changeSign(im, imaginary));
+    private static Complex acos(final double real, final double imaginary, 
+                                final ComplexConstructor constructor) {
+        // Compute with positive values and determine sign at the end
+        double x = Math.abs(real);
+        double y = Math.abs(imaginary);
+        // The result (without sign correction)
+        double re;
+        double im;
+
+        // Handle C99 special cases
+        if (isPosInfinite(x)) {
+            if (isPosInfinite(y)) {
+                re = PI_OVER_4;
+                im = y;
+            } else if (Double.isNaN(y)) {
+                // sign of the imaginary part of the result is unspecified
+                return constructor.create(imaginary, real);
+            } else {
+                re = 0;
+                im = Double.POSITIVE_INFINITY;
             }
-            if (Double.isInfinite(imaginary)) {
-                return constructor.create(PI_OVER_2, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+        } else if (Double.isNaN(x)) {
+            if (isPosInfinite(y)) {
+                return constructor.create(x, -imaginary);
             }
-            // imaginary is NaN
-            // Special case for real == 0
-            return real == 0 ? constructor.create(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 constructor.create(re, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+            // No-use of the input constructor
+            return NAN;
+        } else if (isPosInfinite(y)) {
+            re = PI_OVER_2;
+            im = y;
+        } else if (Double.isNaN(y)) {
+            return constructor.create(x == 0 ? PI_OVER_2 : y, y);
+        } else {
+            // Special case for real numbers:
+            if (y == 0 && x <= 1) {
+                return constructor.create(x == 0 ? PI_OVER_2 : Math.acos(real), -imaginary);
             }
-            if (Double.isInfinite(imaginary)) {
-                final double re = real == Double.NEGATIVE_INFINITY ? PI_3_OVER_4 : PI_OVER_4;
-                return constructor.create(re, Math.copySign(Double.POSITIVE_INFINITY, -imaginary));
+
+            double xp1 = x + 1;
+            double xm1 = x - 1;
+
+            if ((x < SAFE_MAX) && (x > SAFE_MIN) && (y < SAFE_MAX) && (y > SAFE_MIN)) {
+                double yy = y * y;
+                double r = Math.sqrt(xp1 * xp1 + yy);
+                double s = Math.sqrt(xm1 * xm1 + yy);
+                double a = 0.5 * (r + s);
+                double b = x / a;
+
+                if (b <= B_CROSSOVER) {
+                    re = Math.acos(b);
+                } else {
+                    double apx = a + x;
+                    if (x <= 1) {
+                        re = Math.atan(Math.sqrt(0.5 * apx * (yy / (r + xp1) + (s - xm1))) / x);
+                    } else {
+                        re = Math.atan((y * Math.sqrt(0.5 * (apx / (r + xp1) + apx / (s + xm1)))) / x);
+                    }
+                }
+
+                if (a <= A_CROSSOVER) {
+                    double am1;
+                    if (x < 1) {
+                        am1 = 0.5 * (yy / (r + xp1) + yy / (s - xm1));
+                    } else {
+                        am1 = 0.5 * (yy / (r + xp1) + (s + xm1));
+                    }
+                    im = Math.log1p(am1 + Math.sqrt(am1 * (a + 1)));
+                } else {
+                    im = Math.log(a + Math.sqrt(a * a - 1));
+                }
+            } else {
+                // Hull et al: Exception handling code from figure 6
+                if (y <= (EPSILON * Math.abs(xm1))) {
+                    if (x < 1) {
+                        re = Math.acos(x);
+                        im = y / Math.sqrt(xp1 * (1 - x));
+                    } else {
+                        // This deviates from Hull et al's paper as per
+                        // https://svn.boost.org/trac/boost/ticket/7290
+                        if ((Double.MAX_VALUE / xp1) > xm1) {
+                            // xp1 * xm1 won't overflow:
+                            re = y / Math.sqrt(xm1 * xp1);
+                            im = Math.log1p(xm1 + Math.sqrt(xp1 * xm1));
+                        } else {
+                            re = y / x;
+                            im = LN_2 + Math.log(x);
+                        }
+                    }
+                } else if (y <= SAFE_MIN) {
+                    // Hull et al: Assume x == 1.
+                    // True if:
+                    // E^2 > 8*sqrt(u)
+                    //
+                    // E = Machine epsilon: (1 + epsilon) = 1
+                    // u = Double.MIN_NORMAL
+                    re = Math.sqrt(y);
+                    im = Math.sqrt(y);
+                } else if (EPSILON * y - 1 >= x) {
+                    re = PI_OVER_2;
+                    im = LN_2 + Math.log(y);
+                } else if (x > 1) {
+                    re = Math.atan(y / x);
+                    double xoy = x / y;
+                    im = LN_2 + Math.log(y) + 0.5 * Math.log1p(xoy * xoy);
+                } else {
+                    re = PI_OVER_2;
+                    double a = Math.sqrt(1 + y * y);
+                    im = 0.5 * Math.log1p(2 * y * (y + a));
+                }
             }
-            // imaginary is NaN
-            // Swap real and imaginary
-            return constructor.create(Double.NaN, real);
-        }
-        // real is NaN
-        if (Double.isInfinite(imaginary)) {
-            return constructor.create(Double.NaN, -imaginary);
         }
-        // optionally raises the ‘‘invalid’’ floating-point exception, for finite y.
-        return NAN;
+
+        return constructor.create(negative(real) ? Math.PI - re : re,
+                                  negative(imaginary) ? im : -im);
     }
 
     /**
@@ -1345,7 +1433,7 @@ public final class Complex implements Serializable  {
      * <p>This is implemented using real {@code x} and imaginary {@code y} parts:</p>
      * <pre>
      * <code>
-     *   asin(z) = asin(B) + i sign(y)log(A + sqrt(A<sup>2</sup>-1))
+     *   asin(z) = asin(B) + i sign(y)ln(A + sqrt(A<sup>2</sup>-1))
      *   A = 0.5 [ sqrt((x+1)<sup>2</sup>+y<sup>2</sup>) + sqrt((x-1)<sup>2</sup>+y<sup>2</sup>) ]
      *   B = 0.5 [ sqrt((x+1)<sup>2</sup>+y<sup>2</sup>) - sqrt((x-1)<sup>2</sup>+y<sup>2</sup>) ]
      *   sign(y) = {@link Math#copySign(double,double) copySign(1.0, y)}
@@ -1384,7 +1472,8 @@ public final class Complex implements Serializable  {
      * @param constructor Constructor.
      * @return the inverse sine of this complex number
      */
-    private static Complex asin(double real, double imaginary, ComplexConstructor constructor) {
+    private static Complex asin(final double real, final double imaginary, 
+                                final ComplexConstructor constructor) {
         // Compute with positive values and determine sign at the end
         double x = Math.abs(real);
         double y = Math.abs(imaginary);
@@ -1497,7 +1586,8 @@ public final class Complex implements Serializable  {
             }
         }
 
-        return constructor.create(changeSign(re, real), changeSign(im, imaginary));
+        return constructor.create(changeSign(re, real),
+                                  changeSign(im, imaginary));
     }
 
     /**
@@ -1733,16 +1823,13 @@ public final class Complex implements Serializable  {
         if (Double.isNaN(imaginary) && Double.isFinite(real)) {
             return NAN;
         }
-        // ISO C99: Preserve the equality
-        // acos(conj(z)) = conj(acos(z))
-        // by always computing on a positive imaginary Complex number.
-        return acos(real, Math.abs(imaginary), (re, im) ->
-            // Set the sign appropriately for C99 equalities.
-            (im > 0) ?
-                // Multiply by -I and map back to the correct sign
-                new Complex(im, changeSign(-re, imaginary)) :
+        return acos(real, imaginary, (re, im) ->
+            // Set the sign appropriately for real >= 0
+            (negative(im)) ?
                 // Multiply by I
-                new Complex(-im, changeSign(re, imaginary))
+                new Complex(-im, re) :
+                // Multiply by -I
+                new Complex(im, -re)
         );
     }