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>