@@ -102,48 +102,17 @@ impl LogFunc {
102102 }
103103}
104104
105- /// Checks if the base is valid for the efficient integer logarithm algorithm.
106- #[ inline]
107- fn is_valid_integer_base ( base : f64 ) -> bool {
108- base. trunc ( ) == base && base >= 2.0 && base <= u32:: MAX as f64
109- }
110-
111105/// Generic function to calculate logarithm of a decimal value using the given base.
112106///
113- /// For integer bases >= 2 with non-negative scale, uses the efficient integer `ilog` algorithm.
114- /// For all other cases (non-integer bases, negative bases, non-finite bases),
115- /// falls back to f64 computation which naturally returns NaN for invalid inputs,
116- /// matching the behavior of `f64::log`.
107+ /// Uses f64 computation which naturally returns NaN for invalid inputs
108+ /// (base <= 1, non-finite, value <= 0), matching the behavior of `f64::log`.
117109fn log_decimal < T > ( value : T , scale : i8 , base : f64 ) -> Result < f64 , ArrowError >
118110where
119111 T : ToPrimitive + Copy ,
120112{
121- // For integer bases >= 2 and non-negative scale, try the efficient integer algorithm
122- if is_valid_integer_base ( base)
123- && scale >= 0
124- && let Some ( unscaled) = unscale_decimal_value ( & value, scale)
125- {
126- return if unscaled > 0 {
127- Ok ( unscaled. ilog ( base as u128 ) as f64 )
128- } else {
129- Ok ( f64:: NAN )
130- } ;
131- }
132-
133- // Fallback to f64 computation for non-integer bases, negative scale, etc.
134- // This naturally returns NaN for invalid inputs (base <= 1, non-finite, value <= 0)
135113 decimal_to_f64 ( & value, scale) . map ( |v| v. log ( base) )
136114}
137115
138- /// Unscale a decimal value by dividing by 10^scale, returning the result as u128.
139- /// Returns None if the value is negative or the conversion fails.
140- #[ inline]
141- fn unscale_decimal_value < T : ToPrimitive > ( value : & T , scale : i8 ) -> Option < u128 > {
142- let value_u128 = value. to_u128 ( ) ?;
143- let divisor = 10u128 . checked_pow ( scale as u32 ) ?;
144- Some ( value_u128 / divisor)
145- }
146-
147116/// Convert a scaled decimal value to f64.
148117#[ inline]
149118fn decimal_to_f64 < T : ToPrimitive > ( value : & T , scale : i8 ) -> Result < f64 , ArrowError > {
@@ -408,13 +377,10 @@ mod tests {
408377 #[ test]
409378 fn test_log_decimal_native ( ) {
410379 let value = 10_i128 . pow ( 35 ) ;
411- assert_eq ! ( ( value as f64 ) . log2( ) , 116.26748332105768 ) ;
412- assert_eq ! (
413- log_decimal( value, 0 , 2.0 ) . unwrap( ) ,
414- // TODO: see we're losing our decimal points compared to above
415- // https://github.com/apache/datafusion/issues/18524
416- 116.0
417- ) ;
380+ let expected = ( value as f64 ) . log2 ( ) ;
381+ assert_eq ! ( expected, 116.26748332105768 ) ;
382+ // Now using f64 computation, we get the precise value
383+ assert ! ( ( log_decimal( value, 0 , 2.0 ) . unwrap( ) - expected) . abs( ) < 1e-10 ) ;
418384 }
419385
420386 #[ test]
@@ -982,7 +948,8 @@ mod tests {
982948 assert ! ( ( floats. value( 1 ) - 2.0 ) . abs( ) < 1e-10 ) ;
983949 assert ! ( ( floats. value( 2 ) - 3.0 ) . abs( ) < 1e-10 ) ;
984950 assert ! ( ( floats. value( 3 ) - 4.0 ) . abs( ) < 1e-10 ) ;
985- assert ! ( ( floats. value( 4 ) - 4.0 ) . abs( ) < 1e-10 ) ; // Integer rounding
951+ // log10(12600) ≈ 4.1003 (not truncated to 4)
952+ assert ! ( ( floats. value( 4 ) - 12600f64 . log10( ) ) . abs( ) < 1e-10 ) ;
986953 assert ! ( floats. value( 5 ) . is_nan( ) ) ;
987954 }
988955 ColumnarValue :: Scalar ( _) => {
@@ -1117,8 +1084,10 @@ mod tests {
11171084 assert ! ( ( floats. value( 1 ) - 2.0 ) . abs( ) < 1e-10 ) ;
11181085 assert ! ( ( floats. value( 2 ) - 3.0 ) . abs( ) < 1e-10 ) ;
11191086 assert ! ( ( floats. value( 3 ) - 4.0 ) . abs( ) < 1e-10 ) ;
1120- assert ! ( ( floats. value( 4 ) - 4.0 ) . abs( ) < 1e-10 ) ; // Integer rounding for float log
1121- assert ! ( ( floats. value( 5 ) - 38.0 ) . abs( ) < 1e-10 ) ;
1087+ // log10(12600) ≈ 4.1003 (not truncated to 4)
1088+ assert ! ( ( floats. value( 4 ) - 12600f64 . log10( ) ) . abs( ) < 1e-10 ) ;
1089+ // log10(i128::MAX - 1000) ≈ 38.23 (not truncated to 38)
1090+ assert ! ( ( floats. value( 5 ) - ( ( i128 :: MAX - 1000 ) as f64 ) . log10( ) ) . abs( ) < 1e-10 ) ;
11221091 assert ! ( floats. value( 6 ) . is_nan( ) ) ;
11231092 }
11241093 ColumnarValue :: Scalar ( _) => {
@@ -1127,40 +1096,6 @@ mod tests {
11271096 }
11281097 }
11291098
1130- #[ test]
1131- fn test_log_decimal128_invalid_base ( ) {
1132- // Invalid base (-2.0) should return NaN, matching f64::log behavior
1133- let arg_fields = vec ! [
1134- Field :: new( "b" , DataType :: Float64 , false ) . into( ) ,
1135- Field :: new( "x" , DataType :: Decimal128 ( 38 , 0 ) , false ) . into( ) ,
1136- ] ;
1137- let args = ScalarFunctionArgs {
1138- args : vec ! [
1139- ColumnarValue :: Scalar ( ScalarValue :: Float64 ( Some ( -2.0 ) ) ) , // base
1140- ColumnarValue :: Scalar ( ScalarValue :: Decimal128 ( Some ( 64 ) , 38 , 0 ) ) , // num
1141- ] ,
1142- arg_fields,
1143- number_rows : 1 ,
1144- return_field : Field :: new ( "f" , DataType :: Float64 , true ) . into ( ) ,
1145- config_options : Arc :: new ( ConfigOptions :: default ( ) ) ,
1146- } ;
1147- let result = LogFunc :: new ( )
1148- . invoke_with_args ( args)
1149- . expect ( "should not error on invalid base" ) ;
1150-
1151- match result {
1152- ColumnarValue :: Array ( arr) => {
1153- let floats = as_float64_array ( & arr)
1154- . expect ( "failed to convert result to a Float64Array" ) ;
1155- assert_eq ! ( floats. len( ) , 1 ) ;
1156- assert ! ( floats. value( 0 ) . is_nan( ) ) ;
1157- }
1158- ColumnarValue :: Scalar ( _) => {
1159- panic ! ( "Expected an array value" )
1160- }
1161- }
1162- }
1163-
11641099 #[ test]
11651100 fn test_log_decimal256_large ( ) {
11661101 // Large Decimal256 values that don't fit in i128 now use f64 fallback
0 commit comments