You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by pt...@apache.org on 2026/03/16 09:59:07 UTC

(spark) branch branch-4.0 updated: [SPARK-55557][SQL][4.0] Hyperbolic functions should not overflow with large inputs

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

ptoth pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new d3f5a125b957 [SPARK-55557][SQL][4.0] Hyperbolic functions should not overflow with large inputs
d3f5a125b957 is described below

commit d3f5a125b95745ca4914f96db2e63075e6da87e9
Author: Marco Gaido <mg...@fbk.eu>
AuthorDate: Mon Mar 16 10:58:55 2026 +0100

    [SPARK-55557][SQL][4.0] Hyperbolic functions should not overflow with large inputs
    
    ### What changes were proposed in this pull request?
    
    As mentioned in https://issues.apache.org/jira/browse/SPARK-55557, for large values (namely, larger than the square root of `Double.MAX:_VALUE`) the `asinh` and `acosh` functions return Infinity, due to overflow. This happens because of the `x * x` operation in the formula to compute them. However, for such large numbers, the expression can be simplified. Indeed, `x +\- 1` for such large numbers has no effect, since the precision is not enough to be sensitive to this operaiton. Hence, [...]
    
    ### Why are the changes needed?
    
    Current code overflows and returns Infinity for asinh and acosh for large values.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, SQL with asinh and acosh with large values do not return infinity anymore. For large negative values as input of acosh, `NaN` is returned.
    
    ### How was this patch tested?
    
    Added UTs.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No.
    
    Closes #54804 from mgaido91/SPARK-55557_4.0.
    
    Authored-by: Marco Gaido <mg...@fbk.eu>
    Signed-off-by: Peter Toth <pe...@gmail.com>
---
 .../sql/catalyst/expressions/mathExpressions.scala | 40 ++++++++++++++++++----
 .../expressions/MathExpressionsSuite.scala         | 16 ++++++++-
 2 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
index 0f74389a9a5f..f1176cc5a611 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
@@ -412,10 +412,28 @@ case class Cosh(child: Expression) extends UnaryMathExpression(math.cosh, "COSH"
   since = "3.0.0",
   group = "math_funcs")
 case class Acosh(child: Expression)
-  extends UnaryMathExpression((x: Double) => StrictMath.log(x + math.sqrt(x * x - 1.0)), "ACOSH") {
+  extends UnaryMathExpression((x: Double) => x match {
+    // in case of large values, the square would lead to Infinity; also, - 1 would be ignored due
+    // to numeric precision. So log(x + sqrt(x * x - 1)) becomes log(2x) = log(2) + log(x) for
+    // positive values.
+    case x if x >= Math.sqrt(Double.MaxValue) =>
+      StrictMath.log(2) + StrictMath.log(x)
+    case x if x < 1 =>
+      Double.NaN
+    case _ => StrictMath.log(x + math.sqrt(x * x - 1.0)) }, "ACOSH") {
   override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
-    defineCodeGen(ctx, ev,
-      c => s"java.lang.StrictMath.log($c + java.lang.Math.sqrt($c * $c - 1.0))")
+    nullSafeCodeGen(ctx, ev, c => {
+      val sm = "java.lang.StrictMath"
+      s"""
+         |if ($c >= ${Math.sqrt(Double.MaxValue)}) {
+         |  ${ev.value} = $sm.log($c) + $sm.log(2);
+         |} else if ($c < 1) {
+         |  ${ev.value} = java.lang.Double.NaN;
+         |} else {
+         |  ${ev.value} = $sm.log($c + java.lang.Math.sqrt($c * $c - 1.0));
+         |}
+         |""".stripMargin
+    })
   }
   override protected def withNewChildInternal(newChild: Expression): Acosh = copy(child = newChild)
 }
@@ -842,12 +860,20 @@ case class Sinh(child: Expression) extends UnaryMathExpression(math.sinh, "SINH"
   group = "math_funcs")
 case class Asinh(child: Expression)
   extends UnaryMathExpression((x: Double) => x match {
-    case Double.NegativeInfinity => Double.NegativeInfinity
+    // in case of large values, the square would lead to Infinity; also, + 1 would be ignored due
+    // to numeric precision. So log(x + sqrt(x * x + 1)) becomes log(2x) = log(2) + log(x) for
+    // positive values. Since the function is symmetric, for large values we can use
+    // signum(x) + log(2|x|)
+    case x if Math.abs(x) >= Math.sqrt(Double.MaxValue) - 1 =>
+      Math.signum(x) * (StrictMath.log(2) + StrictMath.log(Math.abs(x)))
     case _ => StrictMath.log(x + math.sqrt(x * x + 1.0)) }, "ASINH") {
   override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
-    defineCodeGen(ctx, ev, c =>
-      s"$c == Double.NEGATIVE_INFINITY ? Double.NEGATIVE_INFINITY : " +
-      s"java.lang.StrictMath.log($c + java.lang.Math.sqrt($c * $c + 1.0))")
+    defineCodeGen(ctx, ev, c => {
+      val sm = "java.lang.StrictMath"
+      s"$sm.abs($c) >= ${Math.sqrt(Double.MaxValue) - 1} ? " +
+      s"$sm.signum($c) * ($sm.log($sm.abs($c)) + $sm.log(2)) :" +
+      s"$sm.log($c + java.lang.Math.sqrt($c * $c + 1.0))"
+    })
   }
   override protected def withNewChildInternal(newChild: Expression): Asinh = copy(child = newChild)
 }
diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
index 823a6d2ce867..c78a9f563d42 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
@@ -280,7 +280,9 @@ class MathExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
   }
 
   test("acosh") {
-    testUnary(Acosh, (x: Double) => StrictMath.log(x + math.sqrt(x * x - 1.0)))
+    def f: (Double) => Double = (x: Double) => StrictMath.log(x + math.sqrt(x * x - 1.0))
+    testUnary(Acosh, f, (10 to 20).map(_ * 0.1))
+    testUnary(Acosh, f, (-20 to 9).map(_ * 0.1), expectNaN = true)
     checkConsistencyBetweenInterpretedAndCodegen(Cosh, DoubleType)
 
     val nullLit = Literal.create(null, NullType)
@@ -963,4 +965,16 @@ class MathExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
     checkEvaluation(WidthBucket(5.35, 0.024, Double.NegativeInfinity, 5L), null)
     checkEvaluation(WidthBucket(5.35, 0.024, Double.PositiveInfinity, 5L), null)
   }
+
+  test("SPARK-55557: hyperbolic functions should not overflow with large inputs") {
+    checkEvaluation(Asinh(Double.MaxValue), 710.4758600739439)
+    checkEvaluation(Asinh(Math.sqrt(Double.MaxValue)), 355.58450362725193)
+    checkEvaluation(Acosh(Double.MaxValue), 710.4758600739439)
+    checkEvaluation(Acosh(Math.sqrt(Double.MaxValue)), 355.58450362725193)
+    checkEvaluation(Asinh(Double.MinValue), -710.4758600739439)
+    checkEvaluation(Asinh(-Math.sqrt(Double.MaxValue)), -355.58450362725193)
+    checkNaN(Acosh(Double.MinValue))
+    checkNaN(Acosh(-Math.sqrt(Double.MaxValue) + 1))
+    checkNaN(Acosh(-Math.sqrt(Double.MaxValue) + 2))
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org