diff --git a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/MathUtils.scala b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/MathUtils.scala index a4c6c75358f4e..e318a60ce99ab 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/MathUtils.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/MathUtils.scala @@ -123,6 +123,18 @@ object MathUtils { if (r < 0) (r + n) % n else r } + // Casts a rounded double (the result of Math.ceil/Math.floor) to long, throwing an arithmetic + // overflow error when the value cannot be represented as a long. NaN is passed through to the + // JVM cast (which yields 0), matching the previous behavior. Shared by the eval and codegen + // paths of `Ceil`/`Floor` so the two never diverge. + def doubleToLong(value: Double, context: QueryContext): Long = { + if (!value.isNaN && + (value < Long.MinValue.toDouble || value >= Long.MaxValue.toDouble)) { + throw ExecutionErrors.arithmeticOverflowError("long overflow", context = context) + } + value.toLong + } + def withOverflow[A](f: => A, hint: String = "", context: QueryContext = null): A = { try { f 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 7a76968018c0d..81b576146abe8 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 @@ -252,34 +252,6 @@ case class Cbrt(child: Expression) extends UnaryMathExpression(math.cbrt, "CBRT" override protected def withNewChildInternal(newChild: Expression): Cbrt = copy(child = newChild) } -private object CeilFloor { - def doubleToLong(value: Double, context: QueryContext): Long = { - if (!value.isNaN && - (value < Long.MinValue.toDouble || value >= Long.MaxValue.toDouble)) { - throw QueryExecutionErrors.arithmeticOverflowError("long overflow", context = context) - } - value.toLong - } - - def doubleToLongCode( - input: String, - funcName: String, - result: String, - roundedValue: String, - errorContext: String): String = { - s""" - |double $roundedValue = java.lang.Math.$funcName($input); - |if (!java.lang.Double.isNaN($roundedValue) && - | ($roundedValue < (double) java.lang.Long.MIN_VALUE || - | $roundedValue >= (double) java.lang.Long.MAX_VALUE)) { - | throw QueryExecutionErrors.arithmeticOverflowError( - | "long overflow", "", $errorContext); - |} - |$result = (long) $roundedValue; - |""".stripMargin - } -} - case class Ceil(child: Expression, failOnError: Boolean = SQLConf.get.ansiEnabled) extends UnaryMathExpression(math.ceil, "CEIL") with SupportQueryContext { override def dataType: DataType = child.dataType match { @@ -299,7 +271,7 @@ case class Ceil(child: Expression, failOnError: Boolean = SQLConf.get.ansiEnable protected override def nullSafeEval(input: Any): Any = child.dataType match { case LongType => input.asInstanceOf[Long] case DoubleType if failOnError => - CeilFloor.doubleToLong(f(input.asInstanceOf[Double]), getContextOrNull()) + MathUtils.doubleToLong(f(input.asInstanceOf[Double]), getContextOrNull()) case DoubleType => f(input.asInstanceOf[Double]).toLong case DecimalType.Fixed(_, _) => input.asInstanceOf[Decimal].ceil } @@ -311,11 +283,9 @@ case class Ceil(child: Expression, failOnError: Boolean = SQLConf.get.ansiEnable defineCodeGen(ctx, ev, c => s"$c.ceil()") case LongType => defineCodeGen(ctx, ev, c => s"$c") case DoubleType if failOnError => - nullSafeCodeGen(ctx, ev, c => { - val roundedValue = ctx.freshName("roundedValue") - val errorContext = getContextOrNullCode(ctx) - CeilFloor.doubleToLongCode(c, funcName, ev.value, roundedValue, errorContext) - }) + val mathUtils = MathUtils.getClass.getCanonicalName.stripSuffix("$") + defineCodeGen(ctx, ev, c => + s"$mathUtils.doubleToLong(java.lang.Math.$funcName($c), ${getContextOrNullCode(ctx)})") case _ => defineCodeGen(ctx, ev, c => s"(long)(java.lang.Math.${funcName}($c))") } } @@ -591,7 +561,7 @@ case class Floor(child: Expression, failOnError: Boolean = SQLConf.get.ansiEnabl protected override def nullSafeEval(input: Any): Any = child.dataType match { case LongType => input.asInstanceOf[Long] case DoubleType if failOnError => - CeilFloor.doubleToLong(f(input.asInstanceOf[Double]), getContextOrNull()) + MathUtils.doubleToLong(f(input.asInstanceOf[Double]), getContextOrNull()) case DoubleType => f(input.asInstanceOf[Double]).toLong case DecimalType.Fixed(_, _) => input.asInstanceOf[Decimal].floor } @@ -603,11 +573,9 @@ case class Floor(child: Expression, failOnError: Boolean = SQLConf.get.ansiEnabl defineCodeGen(ctx, ev, c => s"$c.floor()") case LongType => defineCodeGen(ctx, ev, c => s"$c") case DoubleType if failOnError => - nullSafeCodeGen(ctx, ev, c => { - val roundedValue = ctx.freshName("roundedValue") - val errorContext = getContextOrNullCode(ctx) - CeilFloor.doubleToLongCode(c, funcName, ev.value, roundedValue, errorContext) - }) + val mathUtils = MathUtils.getClass.getCanonicalName.stripSuffix("$") + defineCodeGen(ctx, ev, c => + s"$mathUtils.doubleToLong(java.lang.Math.$funcName($c), ${getContextOrNullCode(ctx)})") case _ => defineCodeGen(ctx, ev, c => s"(long)(java.lang.Math.${funcName}($c))") } }