@@ -112,12 +112,14 @@ impl PowerFunc {
112112/// 2.5 is represented as 25 with scale 1
113113/// The unscaled result is 25^4 = 390625
114114/// Scale it back to 1: 390625 / 10^4 = 39
115- ///
116- /// Returns error if base is invalid
117115fn pow_decimal_int < T > ( base : T , scale : i8 , exp : i64 ) -> Result < T , ArrowError >
118116where
119117 T : From < i32 > + ArrowNativeTypeOp ,
120118{
119+ if exp < 0 {
120+ return pow_decimal_float ( base, scale, exp as f64 ) ;
121+ }
122+
121123 let scale: u32 = scale. try_into ( ) . map_err ( |_| {
122124 ArrowError :: NotYetImplemented ( format ! (
123125 "Negative scale is not yet supported value: {scale}"
@@ -149,22 +151,112 @@ where
149151
150152/// Binary function to calculate a math power to float exponent
151153/// for scaled integer types.
152- /// Returns error if exponent is negative or non-integer, or base invalid
153154fn pow_decimal_float < T > ( base : T , scale : i8 , exp : f64 ) -> Result < T , ArrowError >
154155where
155156 T : From < i32 > + ArrowNativeTypeOp ,
156157{
157- if !exp. is_finite ( ) || exp. trunc ( ) != exp {
158+ if exp. is_finite ( ) && exp. trunc ( ) == exp && exp >= 0f64 && exp < u32:: MAX as f64 {
159+ return pow_decimal_int ( base, scale, exp as i64 ) ;
160+ }
161+
162+ if !exp. is_finite ( ) {
158163 return Err ( ArrowError :: ComputeError ( format ! (
159- "Cannot use non-integer exp: {exp}"
164+ "Cannot use non-finite exp: {exp}"
165+ ) ) ) ;
166+ }
167+
168+ pow_decimal_float_fallback ( base, scale, exp)
169+ }
170+
171+ /// Fallback implementation using f64 for negative or non-integer exponents.
172+ /// This handles cases that cannot be computed using integer arithmetic.
173+ fn pow_decimal_float_fallback < T > ( base : T , scale : i8 , exp : f64 ) -> Result < T , ArrowError >
174+ where
175+ T : From < i32 > + ArrowNativeTypeOp ,
176+ {
177+ let scale_factor = 10f64 . powi ( scale as i32 ) ;
178+ let base_f64 = format ! ( "{base:?}" )
179+ . parse :: < f64 > ( )
180+ . map ( |v| v / scale_factor)
181+ . map_err ( |_| {
182+ ArrowError :: ComputeError ( format ! ( "Cannot convert base {base:?} to f64" ) )
183+ } ) ?;
184+
185+ let result_f64 = base_f64. powf ( exp) ;
186+
187+ if !result_f64. is_finite ( ) {
188+ return Err ( ArrowError :: ArithmeticOverflow ( format ! (
189+ "Result of {base_f64}^{exp} is not finite"
160190 ) ) ) ;
161191 }
162- if exp < 0f64 || exp >= u32:: MAX as f64 {
192+
193+ let result_scaled = result_f64 * scale_factor;
194+ let result_rounded = result_scaled. round ( ) ;
195+
196+ if result_rounded. abs ( ) > i128:: MAX as f64 {
163197 return Err ( ArrowError :: ArithmeticOverflow ( format ! (
164- "Unsupported exp value: {exp} "
198+ "Result {result_rounded} is too large for the target decimal type "
165199 ) ) ) ;
166200 }
167- pow_decimal_int ( base, scale, exp as i64 )
201+
202+ decimal_from_i128 :: < T > ( result_rounded as i128 )
203+ }
204+
205+ fn decimal_from_i128 < T > ( value : i128 ) -> Result < T , ArrowError >
206+ where
207+ T : From < i32 > + ArrowNativeTypeOp ,
208+ {
209+ if value == 0 {
210+ return Ok ( T :: from ( 0 ) ) ;
211+ }
212+
213+ if value >= i32:: MIN as i128 && value <= i32:: MAX as i128 {
214+ return Ok ( T :: from ( value as i32 ) ) ;
215+ }
216+
217+ let is_negative = value < 0 ;
218+ let abs_value = value. unsigned_abs ( ) ;
219+
220+ let billion = 1_000_000_000u128 ;
221+ let mut result = T :: from ( 0 ) ;
222+ let mut multiplier = T :: from ( 1 ) ;
223+ let billion_t = T :: from ( 1_000_000_000 ) ;
224+
225+ let mut remaining = abs_value;
226+ while remaining > 0 {
227+ let chunk = ( remaining % billion) as i32 ;
228+ remaining /= billion;
229+
230+ let chunk_value = T :: from ( chunk) . mul_checked ( multiplier) . map_err ( |_| {
231+ ArrowError :: ArithmeticOverflow ( format ! (
232+ "Overflow while converting {value} to decimal type"
233+ ) )
234+ } ) ?;
235+
236+ result = result. add_checked ( chunk_value) . map_err ( |_| {
237+ ArrowError :: ArithmeticOverflow ( format ! (
238+ "Overflow while converting {value} to decimal type"
239+ ) )
240+ } ) ?;
241+
242+ if remaining > 0 {
243+ multiplier = multiplier. mul_checked ( billion_t) . map_err ( |_| {
244+ ArrowError :: ArithmeticOverflow ( format ! (
245+ "Overflow while converting {value} to decimal type"
246+ ) )
247+ } ) ?;
248+ }
249+ }
250+
251+ if is_negative {
252+ result = T :: from ( 0 ) . sub_checked ( result) . map_err ( |_| {
253+ ArrowError :: ArithmeticOverflow ( format ! (
254+ "Overflow while negating {value} in decimal type"
255+ ) )
256+ } ) ?;
257+ }
258+
259+ Ok ( result)
168260}
169261
170262impl ScalarUDFImpl for PowerFunc {
@@ -392,4 +484,38 @@ mod tests {
392484 "Not yet implemented: Negative scale is not yet supported value: -1"
393485 ) ;
394486 }
487+
488+ #[ test]
489+ fn test_pow_decimal_float_fallback ( ) {
490+ // Test negative exponent: 4^(-1) = 0.25
491+ // 4 with scale 2 = 400, result should be 25 (0.25 with scale 2)
492+ let result: i128 = pow_decimal_float ( 400i128 , 2 , -1.0 ) . unwrap ( ) ;
493+ assert_eq ! ( result, 25 ) ;
494+
495+ // Test non-integer exponent: 4^0.5 = 2
496+ // 4 with scale 2 = 400, result should be 200 (2.0 with scale 2)
497+ let result: i128 = pow_decimal_float ( 400i128 , 2 , 0.5 ) . unwrap ( ) ;
498+ assert_eq ! ( result, 200 ) ;
499+
500+ // Test 8^(1/3) = 2 (cube root)
501+ // 8 with scale 1 = 80, result should be 20 (2.0 with scale 1)
502+ let result: i128 = pow_decimal_float ( 80i128 , 1 , 1.0 / 3.0 ) . unwrap ( ) ;
503+ assert_eq ! ( result, 20 ) ;
504+
505+ // Test negative base with integer exponent still works
506+ // (-2)^3 = -8
507+ // -2 with scale 1 = -20, result should be -80 (-8.0 with scale 1)
508+ let result: i128 = pow_decimal_float ( -20i128 , 1 , 3.0 ) . unwrap ( ) ;
509+ assert_eq ! ( result, -80 ) ;
510+
511+ // Test positive integer exponent goes through fast path
512+ // 2.5^4 = 39.0625
513+ // 25 with scale 1, result should be 390 (39.0 with scale 1) - truncated
514+ let result: i128 = pow_decimal_float ( 25i128 , 1 , 4.0 ) . unwrap ( ) ;
515+ assert_eq ! ( result, 390 ) ; // Uses integer path
516+
517+ // Test non-finite exponent returns error
518+ assert ! ( pow_decimal_float( 100i128 , 2 , f64 :: NAN ) . is_err( ) ) ;
519+ assert ! ( pow_decimal_float( 100i128 , 2 , f64 :: INFINITY ) . is_err( ) ) ;
520+ }
395521}
0 commit comments