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/10 23:57:23 UTC

[commons-numbers] 01/03: Ensure log() and log10() avoid overflow.

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 ff1b7d1d197fac7ce58864a8c89d032132ee53f0
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Tue Dec 10 23:23:08 2019 +0000

    Ensure log() and log10() avoid overflow.
---
 .../apache/commons/numbers/complex/Complex.java    | 54 +++++++++++++++++++---
 1 file changed, 48 insertions(+), 6 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 c9cd5ea..0181189 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
@@ -96,6 +96,21 @@ public final class Complex implements Serializable  {
     }
 
     /**
+     * Define a unary operation on a double.
+     * This is used in the log() and log10() functions.
+     */
+    @FunctionalInterface
+    private interface UnaryOperation {
+        /**
+         * Apply an operation to a value.
+         *
+         * @param value The value.
+         * @return The result.
+         */
+        double apply(double value);
+    }
+
+    /**
      * Private default constructor.
      *
      * @param real Real part.
@@ -1469,13 +1484,11 @@ public final class Complex implements Serializable  {
      * @see #arg()
      */
     public Complex log() {
-        // All edge cases satisfied by the Math library
-        return new Complex(Math.log(abs()),
-                           arg());
+        return log(Math::log);
     }
 
     /**
-     * Compute the base 10 or
+     * Compute the base 10
      * <a href="http://mathworld.wolfram.com/CommonLogarithm.html">
      * common logarithm</a> of this complex number.
      * Implements the formula:
@@ -1489,8 +1502,37 @@ public final class Complex implements Serializable  {
      * @see #arg()
      */
     public Complex log10() {
-        // All edge cases satisfied by the Math library
-        return new Complex(Math.log10(abs()),
+        return log(Math::log10);
+    }
+
+    /**
+     * Compute the logarithm of this complex number using the provided function.
+     * Implements the formula:
+     * <pre>
+     *   log(a +  bi) = log(|a + b i|) + i arg(a + b i)
+     * </pre>
+     *
+     * @param log Log function.
+     * @return the logarithm of {@code this}.
+     * @see #abs()
+     * @see #arg()
+     */
+    private Complex log(UnaryOperation log) {
+        // All ISO C99 edge cases satisfied by the Math library.
+        // Make computation overflow safe.
+        final double abs = abs();
+        if (abs == Double.POSITIVE_INFINITY && isFinite()) {
+            // Edge-case where the |a + b i| overflows.
+            // |a + b i| = sqrt(a^2 + b^2)
+            // This can be scaled linearly.
+            // Scale the absolute and exploit:
+            // ln(abs / s) = ln(abs) - ln(s)
+            // ln(abs) = ln(abs / s) + ln(s)
+            final double s = Math.max(Math.abs(real), Math.abs(imaginary));
+            final double absOs = Math.hypot(real / s, imaginary / s);
+            return new Complex(log.apply(absOs) + log.apply(s), arg());
+        }
+        return new Complex(log.apply(abs),
                            arg());
     }