Skip to content

Commit ece37a4

Browse files
committed
feat: Allow log with non-integer base on decimals
1 parent 7900cd6 commit ece37a4

File tree

2 files changed

+26
-13
lines changed

2 files changed

+26
-13
lines changed

datafusion/functions/src/math/log.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,32 +102,39 @@ impl LogFunc {
102102
}
103103
}
104104

105-
/// Binary function to calculate an integer logarithm of Decimal128 `value` using `base` base
106-
/// Returns error if base is invalid
107105
fn log_decimal128(value: i128, scale: i8, base: f64) -> Result<f64, ArrowError> {
108-
if !base.is_finite() || base.trunc() != base {
106+
if !base.is_finite() {
109107
return Err(ArrowError::ComputeError(format!(
110-
"Log cannot use non-integer base: {base}"
108+
"Log cannot use non-finite base: {base}"
111109
)));
112110
}
113-
if (base as u32) < 2 {
111+
if base <= 1.0 {
114112
return Err(ArrowError::ComputeError(format!(
115113
"Log base must be greater than 1: {base}"
116114
)));
117115
}
118116

119-
let unscaled_value = decimal128_to_i128(value, scale)?;
120-
if unscaled_value > 0 {
121-
let log_value: u32 = unscaled_value.ilog(base as i128);
122-
Ok(log_value as f64)
117+
// For integer bases >= 2, use the efficient integer algorithm
118+
if base.trunc() == base && base >= 2.0 && base <= u32::MAX as f64 {
119+
let unscaled_value = decimal128_to_i128(value, scale)?;
120+
if unscaled_value > 0 {
121+
let log_value: u32 = unscaled_value.ilog(base as i128);
122+
return Ok(log_value as f64);
123+
} else {
124+
return Ok(f64::NAN);
125+
}
126+
}
127+
128+
// For non-integer bases, fallback to f64 computation
129+
let scale_factor = 10f64.powi(scale as i32);
130+
let value_f64 = (value as f64) / scale_factor;
131+
if value_f64 > 0.0 {
132+
Ok(value_f64.log(base))
123133
} else {
124-
// Reflect f64::log behaviour
125134
Ok(f64::NAN)
126135
}
127136
}
128137

129-
/// Binary function to calculate an integer logarithm of Decimal128 `value` using `base` base
130-
/// Returns error if base is invalid or if value is out of bounds of Decimal128
131138
fn log_decimal256(value: i256, scale: i8, base: f64) -> Result<f64, ArrowError> {
132139
match value.to_i128() {
133140
Some(value) => log_decimal128(value, scale, base),

datafusion/sqllogictest/test_files/decimal.slt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,14 +878,20 @@ select log(2, 100000000000000000000000000000000000::decimal(38,0));
878878
----
879879
116
880880

881-
# log(10^35) for decimal128 with another base
881+
# log(10^35) for decimal128 with another base (float base)
882882
# TODO: this should be 116.267483321058, error with native decimal log impl
883883
# https://github.com/apache/datafusion/issues/18524
884884
query R
885885
select log(2.0, 100000000000000000000000000000000000::decimal(38,0));
886886
----
887887
116
888888

889+
# log with non-integer base now works (fallback to f64)
890+
query R
891+
select log(2.5, 100::decimal(38,0));
892+
----
893+
5.025883189464
894+
889895
# null cases
890896
query R
891897
select log(null, 100);

0 commit comments

Comments
 (0)