You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/06/04 09:39:20 UTC

(datafusion) branch gh-readonly-queue/main/pr-22651-5472d00856a1d0f6c36afb415ba89f9defff1aeb created (now 12a9b11844)

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

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-22651-5472d00856a1d0f6c36afb415ba89f9defff1aeb
in repository https://gitbox.apache.org/repos/asf/datafusion.git


      at 12a9b11844 fix: Remove `power(decimal, int)` code path (#22651)

This branch includes the following new commits:

     new 12a9b11844 fix: Remove `power(decimal, int)` code path (#22651)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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


(datafusion) 01/01: fix: Remove `power(decimal, int)` code path (#22651)

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-22651-5472d00856a1d0f6c36afb415ba89f9defff1aeb
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit 12a9b1184406756655c8597305bd96c29ab6734a
Author: Neil Conway <ne...@gmail.com>
AuthorDate: Thu Jun 4 05:38:58 2026 -0400

    fix: Remove `power(decimal, int)` code path (#22651)
    
    ## Which issue does this PR close?
    
    - Closes #22480
    - Closes #22510
    
    ## Rationale for this change
    
    `power(decimal, int)` attempted to compute `power` without loss of
    precision. For negative exponents, the code did the computation in
    `Float64` and then cast the result back to `decimal`. Unfortunately, the
    previous implementation got this wrong, because `decimal` might not have
    enough precision to accurately represent the result. It seems simpler to
    return `Float64` for the negative exponent case.
    
    We could potentially try to return `decimal` only for non-negative
    exponents and `Float64` for negative exponents, but that is complicated,
    and also means that the code would produce different results for literal
    arguments vs. columnar arguments, which I think should be avoided.
    
    On reflection, it seems simplest to just remove the `power(decimal,
    int)` code path entirely, and have `power` always return `Float64`. This
    also fixes another issue in the decimal code path (#22480)
    
    ## What changes are included in this PR?
    
    * Remove `power(decimal, ...)` support; both args will be coerced to
    Float64 if necessary, and the function will always return Float64
    * Update SLT
    * Add new test cases for #22480 and #22510
    
    ## Are these changes tested?
    
    Yes.
    
    ## Are there any user-facing changes?
    
    Yes, `power(decimal, ...)` will now return `Float64`.
---
 datafusion/core/src/execution/session_state.rs   |   4 +-
 datafusion/core/tests/expr_api/simplification.rs |  17 +-
 datafusion/functions/src/math/power.rs           | 429 ++---------------------
 datafusion/sqllogictest/test_files/decimal.slt   |  67 ++--
 datafusion/sqllogictest/test_files/math.slt      |   2 +-
 5 files changed, 89 insertions(+), 430 deletions(-)

diff --git a/datafusion/core/src/execution/session_state.rs b/datafusion/core/src/execution/session_state.rs
index 786450c001..ed2ea27cf4 100644
--- a/datafusion/core/src/execution/session_state.rs
+++ b/datafusion/core/src/execution/session_state.rs
@@ -30,7 +30,9 @@ use crate::datasource::provider_as_source;
 use crate::execution::SessionStateDefaults;
 use crate::execution::context::{EmptySerializerRegistry, FunctionFactory, QueryPlanner};
 use crate::physical_planner::{DefaultPhysicalPlanner, PhysicalPlanner};
-use arrow_schema::{DataType, FieldRef};
+#[cfg(feature = "sql")]
+use arrow_schema::DataType;
+use arrow_schema::FieldRef;
 use datafusion_catalog::MemoryCatalogProviderList;
 use datafusion_catalog::information_schema::{
     INFORMATION_SCHEMA, InformationSchemaProvider,
diff --git a/datafusion/core/tests/expr_api/simplification.rs b/datafusion/core/tests/expr_api/simplification.rs
index 6e1271ef19..e9a975239a 100644
--- a/datafusion/core/tests/expr_api/simplification.rs
+++ b/datafusion/core/tests/expr_api/simplification.rs
@@ -639,28 +639,29 @@ fn test_simplify_power() {
     // Power(c3, 0) ===> 1
     {
         let expr = power(col("c3_non_null"), lit(0));
-        let expected = lit(1i64);
+        let expected = lit(1.0f64);
         test_simplify(expr, expected)
     }
-    // Power(c3, 1) ===> c3
+    // Power(c3, 1) ===> cast(c3 AS Float64)
     {
         let expr = power(col("c3_non_null"), lit(1));
-        let expected = col("c3_non_null");
+        let expected =
+            Expr::Cast(Cast::new(Box::new(col("c3_non_null")), DataType::Float64));
         test_simplify(expr, expected)
     }
-    // Power(c3, Log(c3, c4)) ===> cast(c4 AS Int64)
+    // Power(c3, Log(c3, c4)) ===> cast(c4 AS Float64)
     // The simplifier rewrites `power(b, log(b, x))` to `x`, but the
     // rewritten expression must keep the same type as the original
-    // `power` call. `power`'s declared return type follows its base
-    // argument (c3 = Int64), so the UInt32 c4 has to be cast to Int64
-    // to preserve the output schema the optimizer already committed to.
+    // `power` call. `power` returns Float64, so the UInt32 c4 has to be cast
+    // to Float64 to preserve the output schema the optimizer already
+    // committed to.
     {
         let expr = power(
             col("c3_non_null"),
             log(col("c3_non_null"), col("c4_non_null")),
         );
         let expected =
-            Expr::Cast(Cast::new(Box::new(col("c4_non_null")), DataType::Int64));
+            Expr::Cast(Cast::new(Box::new(col("c4_non_null")), DataType::Float64));
         test_simplify(expr, expected)
     }
     // Power(c3, c4) ===> Power(c3, c4)
diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs
index fe8c179bff..252a3ea0b3 100644
--- a/datafusion/functions/src/math/power.rs
+++ b/datafusion/functions/src/math/power.rs
@@ -18,25 +18,20 @@
 //! Math function: `power()`.
 use super::log::LogFunc;
 
-use crate::utils::{calculate_binary_decimal_math, calculate_binary_math};
+use crate::utils::calculate_binary_math;
 use arrow::array::{Array, ArrayRef};
-use arrow::datatypes::i256;
-use arrow::datatypes::{
-    ArrowNativeType, ArrowNativeTypeOp, DataType, Decimal32Type, Decimal64Type,
-    Decimal128Type, Decimal256Type, Float64Type, Int64Type,
-};
+use arrow::datatypes::{DataType, Float64Type};
 use arrow::error::ArrowError;
-use datafusion_common::types::{NativeType, logical_float64, logical_int64};
+use datafusion_common::types::{NativeType, logical_float64};
 use datafusion_common::utils::take_function_args;
 use datafusion_common::{Result, ScalarValue, internal_err};
 use datafusion_expr::expr::ScalarFunction;
 use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
 use datafusion_expr::{
     Cast, Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDF,
-    ScalarUDFImpl, Signature, TypeSignature, TypeSignatureClass, Volatility, lit,
+    ScalarUDFImpl, Signature, TypeSignatureClass, Volatility, lit,
 };
 use datafusion_macros::user_doc;
-use num_traits::{NumCast, ToPrimitive};
 
 /// Matches PostgreSQL: `power(0::float8, negative)` is undefined (IEEE 754 would yield infinity).
 #[inline]
@@ -78,295 +73,18 @@ impl Default for PowerFunc {
 
 impl PowerFunc {
     pub fn new() -> Self {
-        let integer = Coercion::new_implicit(
-            TypeSignatureClass::Native(logical_int64()),
-            vec![TypeSignatureClass::Integer],
-            NativeType::Int64,
-        );
-        let decimal = Coercion::new_exact(TypeSignatureClass::Decimal);
         let float = Coercion::new_implicit(
             TypeSignatureClass::Native(logical_float64()),
             vec![TypeSignatureClass::Numeric],
             NativeType::Float64,
         );
         Self {
-            signature: Signature::one_of(
-                vec![
-                    TypeSignature::Coercible(vec![decimal, integer]),
-                    TypeSignature::Coercible(vec![float; 2]),
-                ],
-                Volatility::Immutable,
-            ),
+            signature: Signature::coercible(vec![float; 2], Volatility::Immutable),
             aliases: vec![String::from("pow")],
         }
     }
 }
 
-/// Binary function to calculate a math power to integer exponent
-/// for scaled integer types.
-///
-/// Formula
-/// The power for a scaled integer `b` is
-///
-/// ```text
-/// (b * 10^(-s)) ^ e
-/// ```
-/// However, the result should be scaled back from scale 0 to scale `s`,
-/// which is done by multiplying by `10^s`.
-/// At the end, the formula is:
-///
-/// ```text
-///   b^e * 10^(-s * e) * 10^s = b^e / 10^(s * (e-1))
-/// ```
-/// Example of 2.5 ^ 4 = 39:
-///   2.5 is represented as 25 with scale 1
-///   The unscaled result is 25^4 = 390625
-///   Scale it back to 1: 390625 / 10^4 = 39
-fn pow_decimal_int<T>(base: T, scale: i8, exp: i64) -> Result<T, ArrowError>
-where
-    T: ArrowNativeType + ArrowNativeTypeOp + ToPrimitive + NumCast + Copy,
-{
-    // Negative exponent: fall back to float computation
-    if exp < 0 {
-        return pow_decimal_float(base, scale, exp as f64);
-    }
-
-    let exp: u32 = exp.try_into().map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Unsupported exp value: {exp}"))
-    })?;
-    // Handle edge case for exp == 0
-    // If scale < 0, 10^scale (e.g., 10^-2 = 0.01) becomes 0 in integer arithmetic.
-    if exp == 0 {
-        return if scale >= 0 {
-            T::usize_as(10).pow_checked(scale as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make unscale factor for {scale} and {exp}"
-                ))
-            })
-        } else {
-            Ok(T::ZERO)
-        };
-    }
-    let powered: T = base.pow_checked(exp).map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Cannot raise base {base:?} to exp {exp}"))
-    })?;
-
-    // Calculate the scale adjustment: s * (e - 1)
-    // We use i64 to prevent overflow during the intermediate multiplication
-    let mul_exp = (scale as i64).wrapping_mul(exp as i64 - 1);
-
-    if mul_exp == 0 {
-        return Ok(powered);
-    }
-
-    // If mul_exp is positive, we divide (standard case).
-    // If mul_exp is negative, we multiply (negative scale case).
-    if mul_exp > 0 {
-        let div_factor: T =
-            T::usize_as(10).pow_checked(mul_exp as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make div factor for {scale} and {exp}"
-                ))
-            })?;
-        powered.div_checked(div_factor)
-    } else {
-        // mul_exp is negative, so we multiply by 10^(-mul_exp)
-        let abs_exp = mul_exp.checked_neg().ok_or_else(|| {
-            ArrowError::ArithmeticOverflow(
-                "Overflow while negating scale exponent".to_string(),
-            )
-        })?;
-        let mul_factor: T =
-            T::usize_as(10).pow_checked(abs_exp as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make mul factor for {scale} and {exp}"
-                ))
-            })?;
-        powered.mul_checked(mul_factor)
-    }
-}
-
-/// Binary function to calculate a math power to float exponent
-/// for scaled integer types.
-fn pow_decimal_float<T>(base: T, scale: i8, exp: f64) -> Result<T, ArrowError>
-where
-    T: ArrowNativeType + ArrowNativeTypeOp + ToPrimitive + NumCast + Copy,
-{
-    if exp.is_finite() && exp.trunc() == exp && exp >= 0f64 && exp < u32::MAX as f64 {
-        return pow_decimal_int(base, scale, exp as i64);
-    }
-
-    if !exp.is_finite() {
-        return Err(ArrowError::ComputeError(format!(
-            "Cannot use non-finite exp: {exp}"
-        )));
-    }
-
-    pow_decimal_float_fallback(base, scale, exp)
-}
-
-/// Compute the f64 power result and scale it back.
-/// Returns the rounded i128 result for conversion to target type.
-#[inline]
-fn compute_pow_f64_result(
-    base_f64: f64,
-    scale: i8,
-    exp: f64,
-) -> Result<i128, ArrowError> {
-    let result_f64 = float64_power_checked(base_f64, exp)?;
-
-    if !result_f64.is_finite() {
-        return Err(ArrowError::ArithmeticOverflow(format!(
-            "Result of {base_f64}^{exp} is not finite"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let result_scaled = result_f64 * scale_factor;
-    let result_rounded = result_scaled.round();
-
-    if result_rounded.abs() > i128::MAX as f64 {
-        return Err(ArrowError::ArithmeticOverflow(format!(
-            "Result {result_rounded} is too large for the target decimal type"
-        )));
-    }
-
-    Ok(result_rounded as i128)
-}
-
-/// Convert i128 result to target decimal native type using NumCast.
-/// Returns error if value overflows the target type.
-#[inline]
-fn decimal_from_i128<T>(value: i128) -> Result<T, ArrowError>
-where
-    T: NumCast,
-{
-    NumCast::from(value).ok_or_else(|| {
-        ArrowError::ArithmeticOverflow(format!(
-            "Value {value} is too large for the target decimal type"
-        ))
-    })
-}
-
-/// Fallback for `pow_decimal_int` when the exponent is negative or non-integer.
-fn pow_decimal_float_fallback<T>(base: T, scale: i8, exp: f64) -> Result<T, ArrowError>
-where
-    T: ToPrimitive + NumCast + Copy,
-{
-    if scale < 0 {
-        return Err(ArrowError::NotYetImplemented(format!(
-            "Negative scale is not yet supported: {scale}"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let base_f64 = base.to_f64().ok_or_else(|| {
-        ArrowError::ComputeError("Cannot convert base to f64".to_string())
-    })? / scale_factor;
-
-    let result_i128 = compute_pow_f64_result(base_f64, scale, exp)?;
-
-    decimal_from_i128(result_i128)
-}
-
-/// Like `pow_decimal_float`, but specialized for Decimal256.
-fn pow_decimal256_float(base: i256, scale: i8, exp: f64) -> Result<i256, ArrowError> {
-    if exp.is_finite() && exp.trunc() == exp && exp >= 0f64 && exp < u32::MAX as f64 {
-        return pow_decimal256_int(base, scale, exp as i64);
-    }
-
-    if !exp.is_finite() {
-        return Err(ArrowError::ComputeError(format!(
-            "Cannot use non-finite exp: {exp}"
-        )));
-    }
-
-    pow_decimal256_float_fallback(base, scale, exp)
-}
-
-/// Like `pow_decimal_int`, but specialized for Decimal256.
-fn pow_decimal256_int(base: i256, scale: i8, exp: i64) -> Result<i256, ArrowError> {
-    if exp < 0 {
-        return pow_decimal256_float(base, scale, exp as f64);
-    }
-
-    let exp: u32 = exp.try_into().map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Unsupported exp value: {exp}"))
-    })?;
-
-    if exp == 0 {
-        return if scale >= 0 {
-            i256::from_i128(10).pow_checked(scale as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make unscale factor for {scale} and {exp}"
-                ))
-            })
-        } else {
-            Ok(i256::from_i128(0))
-        };
-    }
-
-    let powered: i256 = base.pow_checked(exp).map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Cannot raise base {base:?} to exp {exp}"))
-    })?;
-
-    let mul_exp = (scale as i64).wrapping_mul(exp as i64 - 1);
-
-    if mul_exp == 0 {
-        return Ok(powered);
-    }
-
-    if mul_exp > 0 {
-        let div_factor: i256 =
-            i256::from_i128(10)
-                .pow_checked(mul_exp as u32)
-                .map_err(|_| {
-                    ArrowError::ArithmeticOverflow(format!(
-                        "Cannot make div factor for {scale} and {exp}"
-                    ))
-                })?;
-        powered.div_checked(div_factor)
-    } else {
-        let abs_exp = mul_exp.checked_neg().ok_or_else(|| {
-            ArrowError::ArithmeticOverflow(
-                "Overflow while negating scale exponent".to_string(),
-            )
-        })?;
-        let mul_factor: i256 =
-            i256::from_i128(10)
-                .pow_checked(abs_exp as u32)
-                .map_err(|_| {
-                    ArrowError::ArithmeticOverflow(format!(
-                        "Cannot make mul factor for {scale} and {exp}"
-                    ))
-                })?;
-        powered.mul_checked(mul_factor)
-    }
-}
-
-/// Like `pow_decimal_float_fallback`, but specialized for Decimal256.
-fn pow_decimal256_float_fallback(
-    base: i256,
-    scale: i8,
-    exp: f64,
-) -> Result<i256, ArrowError> {
-    if scale < 0 {
-        return Err(ArrowError::NotYetImplemented(format!(
-            "Negative scale is not yet supported: {scale}"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let base_f64 = base.to_f64().ok_or_else(|| {
-        ArrowError::ComputeError("Cannot convert base to f64".to_string())
-    })? / scale_factor;
-
-    let result_i128 = compute_pow_f64_result(base_f64, scale, exp)?;
-
-    // i256 can be constructed from i128 directly
-    Ok(i256::from_i128(result_i128))
-}
-
 impl ScalarUDFImpl for PowerFunc {
     fn name(&self) -> &str {
         "power"
@@ -377,17 +95,8 @@ impl ScalarUDFImpl for PowerFunc {
     }
 
     fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
-        // Return type as a function of (base, exponent). After signature
-        // coercion, we have to handle the following cases:
-        //
-        //   - NULL on either side -> Float64 (typed NULL)
-        //   - (Decimal, Int64)    -> the base's Decimal type
-        //   - (Float64, Float64)  -> Float64
-        let [base, exponent] = take_function_args(self.name(), arg_types)?;
-        if base.is_null() || exponent.is_null() {
-            return Ok(DataType::Float64);
-        }
-        Ok(base.clone())
+        let [_base, _exponent] = take_function_args(self.name(), arg_types)?;
+        Ok(DataType::Float64)
     }
 
     fn aliases(&self) -> &[String] {
@@ -398,18 +107,6 @@ impl ScalarUDFImpl for PowerFunc {
         let [base, exponent] = take_function_args(self.name(), &args.args)?;
         let base = base.to_array(args.number_rows)?;
 
-        macro_rules! decimal_pow_arm {
-            ($decimal_ty:ident, $pow_fn:ident, $precision:expr, $scale:expr) => {
-                calculate_binary_decimal_math::<$decimal_ty, Int64Type, $decimal_ty, _>(
-                    &base,
-                    exponent,
-                    |b, e| $pow_fn(b, *$scale, e),
-                    *$precision,
-                    *$scale,
-                )?
-            };
-        }
-
         let arr: ArrayRef = match (base.data_type(), exponent.data_type()) {
             (DataType::Float64, DataType::Float64) => {
                 calculate_binary_math::<Float64Type, Float64Type, Float64Type, _>(
@@ -418,18 +115,6 @@ impl ScalarUDFImpl for PowerFunc {
                     float64_power_checked,
                 )?
             }
-            (DataType::Decimal32(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal32Type, pow_decimal_int, precision, scale)
-            }
-            (DataType::Decimal64(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal64Type, pow_decimal_int, precision, scale)
-            }
-            (DataType::Decimal128(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal128Type, pow_decimal_int, precision, scale)
-            }
-            (DataType::Decimal256(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal256Type, pow_decimal256_int, precision, scale)
-            }
             (base_type, exp_type) => {
                 return internal_err!(
                     "Unsupported data types for base {base_type:?} and exponent {exp_type:?} for power"
@@ -451,16 +136,31 @@ impl ScalarUDFImpl for PowerFunc {
         let [base, exponent] = take_function_args("power", args)?;
         let base_type = info.get_data_type(&base)?;
         let exponent_type = info.get_data_type(&exponent)?;
+        let return_type =
+            self.return_type(&[base_type.clone(), exponent_type.clone()])?;
 
         // Null propagation
         if base_type.is_null() || exponent_type.is_null() {
-            let return_type = self.return_type(&[base_type, exponent_type])?;
             return Ok(ExprSimplifyResult::Simplified(lit(
                 ScalarValue::Null.cast_to(&return_type)?
             )));
         }
 
-        let return_type = self.return_type(&[base_type, exponent_type.clone()])?;
+        // `simplify` runs on the logical expression *before* type coercion,
+        // so a simplified sub-expression may still carry its original type
+        // rather than the Float64 that `power` is declared to return. Cast it
+        // back when needed to preserve the schema the optimizer already
+        // committed to — e.g. `power(int_col, 1)` simplifies to `int_col`,
+        // and the `b` in `power(b, log(b, uint_col))` simplifies to `uint_col`,
+        // both of which must become Float64.
+        let cast_to_return_type = |expr: Expr, expr_type: &DataType| {
+            if expr_type == &return_type {
+                expr
+            } else {
+                Expr::Cast(Cast::new(Box::new(expr), return_type.clone()))
+            }
+        };
+
         match exponent {
             Expr::Literal(value, _)
                 if value == ScalarValue::new_zero(&exponent_type)? =>
@@ -470,22 +170,18 @@ impl ScalarUDFImpl for PowerFunc {
                 )?)))
             }
             Expr::Literal(value, _) if value == ScalarValue::new_one(&exponent_type)? => {
-                Ok(ExprSimplifyResult::Simplified(base))
+                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
+                    base, &base_type,
+                )))
             }
             Expr::ScalarFunction(ScalarFunction { func, mut args })
                 if is_log(&func) && args.len() == 2 && base == args[0] =>
             {
-                // The inner `b` may have a different type than the power
-                // call's `return_type` (e.g. `power(int64, log(int64,
-                // uint32))` returns Int64 but `b` is UInt32). Wrap it
-                // in a cast to preserve the optimizer's expected schema.
                 let b = args.pop().unwrap(); // length checked above
-                let result = if info.get_data_type(&b)? != return_type {
-                    Expr::Cast(Cast::new(Box::new(b), return_type))
-                } else {
-                    b
-                };
-                Ok(ExprSimplifyResult::Simplified(result))
+                let b_type = info.get_data_type(&b)?;
+                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
+                    b, &b_type,
+                )))
             }
             _ => Ok(ExprSimplifyResult::Original(vec![base, exponent])),
         }
@@ -505,69 +201,6 @@ fn is_log(func: &ScalarUDF) -> bool {
 mod tests {
     use super::*;
 
-    #[test]
-    fn test_pow_decimal128_helper() {
-        // Expression: 2.5 ^ 4 = 39.0625
-        assert_eq!(pow_decimal_int(25i128, 1, 4).unwrap(), 390i128);
-        assert_eq!(pow_decimal_int(2500i128, 3, 4).unwrap(), 39062i128);
-        assert_eq!(pow_decimal_int(25000i128, 4, 4).unwrap(), 390625i128);
-
-        // Expression: 25 ^ 4 = 390625
-        assert_eq!(pow_decimal_int(25i128, 0, 4).unwrap(), 390625i128);
-
-        // Expressions for edge cases
-        assert_eq!(pow_decimal_int(25i128, 1, 1).unwrap(), 25i128);
-        assert_eq!(pow_decimal_int(25i128, 0, 1).unwrap(), 25i128);
-        assert_eq!(pow_decimal_int(25i128, 0, 0).unwrap(), 1i128);
-        assert_eq!(pow_decimal_int(25i128, 1, 0).unwrap(), 10i128);
-
-        assert_eq!(pow_decimal_int(25i128, -1, 4).unwrap(), 390625000i128);
-    }
-
-    #[test]
-    fn test_pow_decimal_float_fallback() {
-        // Test negative exponent: 4^(-1) = 0.25
-        // 4 with scale 2 = 400, result should be 25 (0.25 with scale 2)
-        let result: i128 = pow_decimal_float(400i128, 2, -1.0).unwrap();
-        assert_eq!(result, 25);
-
-        // Test non-integer exponent: 4^0.5 = 2
-        // 4 with scale 2 = 400, result should be 200 (2.0 with scale 2)
-        let result: i128 = pow_decimal_float(400i128, 2, 0.5).unwrap();
-        assert_eq!(result, 200);
-
-        // Test 8^(1/3) = 2 (cube root)
-        // 8 with scale 1 = 80, result should be 20 (2.0 with scale 1)
-        let result: i128 = pow_decimal_float(80i128, 1, 1.0 / 3.0).unwrap();
-        assert_eq!(result, 20);
-
-        // Test negative base with integer exponent still works
-        // (-2)^3 = -8
-        // -2 with scale 1 = -20, result should be -80 (-8.0 with scale 1)
-        let result: i128 = pow_decimal_float(-20i128, 1, 3.0).unwrap();
-        assert_eq!(result, -80);
-
-        // Test positive integer exponent goes through fast path
-        // 2.5^4 = 39.0625
-        // 25 with scale 1, result should be 390 (39.0 with scale 1) - truncated
-        let result: i128 = pow_decimal_float(25i128, 1, 4.0).unwrap();
-        assert_eq!(result, 390); // Uses integer path
-
-        // Test non-finite exponent returns error
-        assert!(pow_decimal_float(100i128, 2, f64::NAN).is_err());
-        assert!(pow_decimal_float(100i128, 2, f64::INFINITY).is_err());
-
-        // PostgreSQL: zero to a negative power is undefined
-        assert!(pow_decimal_float(0i128, 2, -1.0).is_err());
-    }
-
-    #[test]
-    fn test_pow_decimal256_zero_to_negative_exp_errors() {
-        assert!(pow_decimal256_float(i256::ZERO, 2, -1.0).is_err());
-        // Negative integer exponent uses pow_decimal256_float via pow_decimal256_int
-        assert!(pow_decimal256_int(i256::ZERO, 2, -1).is_err());
-    }
-
     #[test]
     fn test_float64_power_checked_zero_negative_exp() {
         assert_eq!(float64_power_checked(0.0, 1.0).unwrap(), 0.0);
diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt
index d9eac84928..dd2b294557 100644
--- a/datafusion/sqllogictest/test_files/decimal.slt
+++ b/datafusion/sqllogictest/test_files/decimal.slt
@@ -1046,17 +1046,17 @@ SELECT log(10, arrow_cast(1 , 'Decimal32(5, 1)'))
 query RT
 SELECT power(2::decimal(38, 0), 4), arrow_typeof(power(2::decimal(38, 0), 4));
 ----
-16 Decimal128(38, 0)
+16 Float64
 
 query RT
 SELECT power(10000000000::decimal(38, 0), 2), arrow_typeof(power(10000000000::decimal(38, 0), 2));
 ----
-100000000000000000000 Decimal128(38, 0)
+100000000000000000000 Float64
 
 query R
 SELECT power(2.5, 4)
 ----
-39
+39.0625
 
 query R
 SELECT power(2.5, 1)
@@ -1093,30 +1093,45 @@ SELECT power(2, 100000000000)
 ----
 Infinity
 
-# Negative exponent now works (fallback to f64)
+# Negative exponent returns Float64 so fractional results are representable
 query RT
 SELECT power(2::decimal(38, 0), -5), arrow_typeof(power(2::decimal(38, 0), -5));
 ----
-0 Decimal128(38, 0)
+0.03125 Float64
+
+query RT
+SELECT power(CAST(2 AS DECIMAL(10, 0)), -3), arrow_typeof(power(CAST(2 AS DECIMAL(10, 0)), -3));
+----
+0.125 Float64
 
-# Negative exponent with scale preserves decimal places
 query RT
 SELECT power(4::decimal(38, 5), -1), arrow_typeof(power(4::decimal(38, 5), -1));
 ----
-0.25 Decimal128(38, 5)
+0.25 Float64
+
+query IRT
+SELECT exponent, power(2::decimal(10, 0), exponent), arrow_typeof(power(2::decimal(10, 0), exponent))
+FROM (VALUES (-3), (3)) AS t(exponent)
+ORDER BY exponent;
+----
+-3 0.125 Float64
+3 8 Float64
 
-# Expected to have `16 Decimal128(38, 0)`
-# Due to type coericion, it becomes Float -> Float -> Float
 query RT
 SELECT power(2::decimal(38, 0), 4), arrow_typeof(power(2::decimal(38, 0), 4));
 ----
-16 Decimal128(38, 0)
+16 Float64
 
-# Arbitrary scale
 query RT
 SELECT power(2.5::decimal(38, 3), 4), arrow_typeof(power(2.5::decimal(38, 3), 4));
 ----
-39.062 Decimal128(38, 3)
+39.0625 Float64
+
+# https://github.com/apache/datafusion/issues/22480
+query RT
+SELECT power(2.5::decimal(20, 4), 10), arrow_typeof(power(2.5::decimal(20, 4), 10));
+----
+9536.7431640625 Float64
 
 query RT
 SELECT power(2.5, 4.0), arrow_typeof(power(2.5, 4.0));
@@ -1147,30 +1162,38 @@ SELECT power(2::decimal(38, 0), 5000000000.1),
 ----
 Infinity Float64
 
-# Integer Above u32::max - still goes through integer path which fails
-query error Arrow error: Arithmetic overflow: Unsupported exp value
-SELECT power(2::decimal(38, 0), 5000000000)
+# Integer above u32::max uses the Float64 decimal/int path
+query RT
+SELECT power(2::decimal(38, 0), 5000000000),
+       arrow_typeof(power(2::decimal(38, 0), 5000000000));
+----
+Infinity Float64
 
-query ?T
+query RT
 SELECT power(arrow_cast(2, 'Decimal32(5, 0)'), 4), arrow_typeof(power(arrow_cast(2, 'Decimal32(5, 0)'), 4));
 ----
-16 Decimal32(5, 0)
+16 Float64
 
-query ?T
+query RT
 SELECT power(arrow_cast(2, 'Decimal64(5, 0)'), 4), arrow_typeof(power(arrow_cast(2, 'Decimal64(5, 0)'), 4));
 ----
-16 Decimal64(5, 0)
+16 Float64
 
 query RT
 SELECT power(2::decimal(76, 0), 4), arrow_typeof(power(2::decimal(76, 0), 4));
 ----
-16 Decimal256(76, 0)
+16 Float64
 
 query R
 SELECT power(2.0, null)
 ----
 NULL
 
+query RT
+SELECT power(2::decimal(38, 0), null), arrow_typeof(power(2::decimal(38, 0), null));
+----
+NULL Float64
+
 # Array variants of power function
 query RR rowsort
 SELECT distinct c1*100000, power(c1*100000, 2) from decimal_simple;
@@ -1214,7 +1237,7 @@ select log(100000000000000000000000000000000000::decimal(38,0))
 ----
 35
 
-# Result is decimal since argument is decimal regardless decimals-as-floats parsing
+# Decimal x Int64 returns Float64 regardless of decimals-as-floats parsing
 query R
 SELECT power(10000000000::decimal(38, 0), 2);
 ----
@@ -1224,7 +1247,7 @@ query RT
 SELECT power(10000000000::decimal(38, 0), 2),
        arrow_typeof(power(10000000000::decimal(38, 0), 2));
 ----
-100000000000000000000 Decimal128(38, 0)
+100000000000000000000 Float64
 
 query R
 SELECT power(2.5, 4.0)
diff --git a/datafusion/sqllogictest/test_files/math.slt b/datafusion/sqllogictest/test_files/math.slt
index e261bada87..1748c9b3e5 100644
--- a/datafusion/sqllogictest/test_files/math.slt
+++ b/datafusion/sqllogictest/test_files/math.slt
@@ -844,7 +844,7 @@ select
   pow(2.5::decimal(2, 1), 4::bigint),
   arrow_typeof(pow(2.5::decimal(2, 1), 4::bigint));
 ----
-39 Decimal128(2, 1)
+39.0625 Float64
 
 # factorial negative (PostgreSQL-compatible domain error)
 query error DataFusion error: Execution error: factorial of a negative number is undefined


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