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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
