This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-math.git


The following commit(s) were added to refs/heads/master by this push:
     new d38ed3d6c MATH-1692: Avoid intermediate overflow in asinh/acosh
d38ed3d6c is described below

commit d38ed3d6c432fdf43f916a87363ecef34752d779
Author: Alex Herbert <[email protected]>
AuthorDate: Thu Oct 8 16:01:47 2026 +0100

    MATH-1692: Avoid intermediate overflow in asinh/acosh
---
 .../commons/math4/core/jdkmath/AccurateMath.java   | 29 ++++++++++++++--
 .../legacy/core/jdkmath/AccurateMathTest.java      | 39 ++++++++++++++++++++++
 src/changes/changes.xml                            |  3 ++
 3 files changed, 69 insertions(+), 2 deletions(-)

diff --git 
a/commons-math-core/src/main/java/org/apache/commons/math4/core/jdkmath/AccurateMath.java
 
b/commons-math-core/src/main/java/org/apache/commons/math4/core/jdkmath/AccurateMath.java
index f7c6ab773..bcbff70ad 100644
--- 
a/commons-math-core/src/main/java/org/apache/commons/math4/core/jdkmath/AccurateMath.java
+++ 
b/commons-math-core/src/main/java/org/apache/commons/math4/core/jdkmath/AccurateMath.java
@@ -86,6 +86,13 @@ public final class AccurateMath {
     private static final String OVERFLOW_MSG = "Overflow";
     /** StrictMath.log(Double.MAX_VALUE): {@value}. */
     private static final double LOG_MAX_VALUE = 
StrictMath.log(Double.MAX_VALUE);
+    /** Threshold used in the inverse hyperbolic sine/cosine where
+     * {@code |a| = sqrt(a * a +/- 1}. 1 cannot be added to or subtracted from 
the square. */
+    private static final double SQUARE_PM_1 = 0x1p27;
+    /** 2^1023. Threshold where a double cannot be multiplied by 2. */
+    private static final double TWO_POW_1023 = 0x1p1023;
+    /** log(2). This is not the same as the high-bits of log(2). */
+    private static final double LN_2 = 0.6931471805599453;
 
     /** Indicator for tables initialization.
      * <p>
@@ -696,7 +703,18 @@ public final class AccurateMath {
      * @return inverse hyperbolic cosine of a
      */
     public static double acosh(final double a) {
-        return AccurateMath.log(a + Math.sqrt(a * a - 1));
+        if (a < 1.0) {
+            // a + sqrt(a * a - 1) < 0 : result is a complex number
+            return Double.NaN;
+        }
+        // Detect a = sqrt(a * a - 1) when 1 cannot be subtracted
+        if (a < SQUARE_PM_1) {
+            return AccurateMath.log(a + Math.sqrt(a * a - 1));
+        } else if (a < TWO_POW_1023) {
+            return AccurateMath.log(2 * a);
+        } else {
+            return AccurateMath.log(a) + LN_2;
+        }
     }
 
     /** Compute the inverse hyperbolic sine of a number.
@@ -712,7 +730,14 @@ public final class AccurateMath {
 
         double absAsinh;
         if (a > 0.167) {
-            absAsinh = AccurateMath.log(Math.sqrt(a * a + 1) + a);
+            // Detect a = sqrt(a * a + 1) when 1 cannot be added
+            if (a < SQUARE_PM_1) {
+                absAsinh = AccurateMath.log(a + Math.sqrt(a * a + 1));
+            } else if (a < TWO_POW_1023) {
+                absAsinh = AccurateMath.log(2 * a);
+            } else {
+                absAsinh = AccurateMath.log(a) + LN_2;
+            }
         } else {
             final double a2 = a * a;
             if (a > 0.097) {
diff --git 
a/commons-math-legacy-core/src/test/java/org/apache/commons/math4/legacy/core/jdkmath/AccurateMathTest.java
 
b/commons-math-legacy-core/src/test/java/org/apache/commons/math4/legacy/core/jdkmath/AccurateMathTest.java
index 75aa126d7..d75f7693f 100644
--- 
a/commons-math-legacy-core/src/test/java/org/apache/commons/math4/legacy/core/jdkmath/AccurateMathTest.java
+++ 
b/commons-math-legacy-core/src/test/java/org/apache/commons/math4/legacy/core/jdkmath/AccurateMathTest.java
@@ -250,6 +250,45 @@ public class AccurateMathTest {
         assertEquals(0, maxErr, 2);
     }
 
+    @Test
+    public void testHyperbolicInversesSpecial() {
+        // Added to JDK 27.
+        // Special cases taken from the javadoc.
+
+        // asinh(a) = sign(a) * log(|a| + sqrt(a * a + 1))
+        // - If the argument is zero, then the result is a zero with the same 
sign as the argument.
+        // - If the argument is infinity, then the result is infinity with the 
same sign as the argument.
+        // - If the argument is NaN, then the result is NaN.
+        Assertions.assertEquals(-0.0, AccurateMath.asinh(-0.0));
+        Assertions.assertEquals(0.0, AccurateMath.asinh(0.0));
+        Assertions.assertEquals(Double.POSITIVE_INFINITY, 
AccurateMath.asinh(Double.POSITIVE_INFINITY));
+        Assertions.assertEquals(Double.NEGATIVE_INFINITY, 
AccurateMath.asinh(Double.NEGATIVE_INFINITY));
+        Assertions.assertEquals(Double.NaN, AccurateMath.asinh(Double.NaN));
+
+        // acosh(a) = log(a + sqrt(a * a - 1))
+        // - If the argument is positive infinity, then the result is positive 
infinity
+        // - If the argument less than 1, then the result is NaN.
+        // - If the argument is NaN, then the result is NaN.
+        // - If the argument is 1.0, then the result is positive zero.
+
+        Assertions.assertEquals(Double.POSITIVE_INFINITY, 
AccurateMath.acosh(Double.POSITIVE_INFINITY));
+        Assertions.assertEquals(Double.NaN, 
AccurateMath.acosh(Math.nextDown(1.0)));
+        Assertions.assertEquals(Double.NaN, AccurateMath.acosh(0.0));
+        Assertions.assertEquals(Double.NaN, 
AccurateMath.acosh(Double.NEGATIVE_INFINITY));
+        Assertions.assertEquals(Double.NaN, AccurateMath.acosh(Double.NaN));
+        Assertions.assertEquals(0.0, AccurateMath.acosh(1.0));
+
+        // MATH-1692: Detect overflow in sqrt(a * a +/- 1) and (2 * a)
+        Assertions.assertEquals(Double.NaN, 
AccurateMath.acosh(-Double.MAX_VALUE));
+        Assertions.assertEquals(-710.4758600739439, 
AccurateMath.asinh(-Double.MAX_VALUE));
+        Assertions.assertEquals(710.4758600739439, 
AccurateMath.asinh(Double.MAX_VALUE));
+        Assertions.assertEquals(710.4758600739439, 
AccurateMath.acosh(Double.MAX_VALUE));
+        // Value from mpmath.asinh version 1.4.1
+        Assertions.assertEquals(-461.210165779369082082748645271, 
AccurateMath.asinh(-1e200));
+        Assertions.assertEquals(461.210165779369082082748645271, 
AccurateMath.asinh(1e200));
+        Assertions.assertEquals(461.210165779369082082748645271, 
AccurateMath.acosh(1e200));
+    }
+
     @Test
     public void testLogAccuracy() {
         double maxerrulp = 0.0;
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7205f590a..dbf868654 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -96,6 +96,9 @@ Caveat:
  to support the whole codebase (it was one of the main reasons for
  creating more focused components).
 ">
+      <action dev="aherbetrt" type="fix" issue="MATH-1692" due-to="Alex 
Herbert">
+        "AccurateMath": Avoid intermediate overflow in asinh and acosh.
+      </action>
       <action dev="erans" type="fix" issue="MATH-1691" due-to="Security scan">
         Miscellaneous hardening fixes.
       </action>

Reply via email to