diff --git a/datafusion/functions/src/math/ln.rs b/datafusion/functions/src/math/ln.rs new file mode 100644 index 0000000000000..24079d27181cc --- /dev/null +++ b/datafusion/functions/src/math/ln.rs @@ -0,0 +1,132 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::sync::Arc; + +use arrow::array::{ArrayRef, AsArray, Float32Array, Float64Array}; +use arrow::datatypes::{DataType, Float32Type, Float64Type}; +use arrow::error::ArrowError; +use datafusion_common::{Result, exec_err, utils::take_function_args}; +use datafusion_expr::interval_arithmetic::Interval; +use datafusion_expr::sort_properties::{ExprProperties, SortProperties}; +use datafusion_expr::{ + ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature, + Volatility, +}; + +use super::{bounds::unbounded_bounds, get_ln_doc, ln_order}; + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct LnFunc { + signature: Signature, +} + +impl Default for LnFunc { + fn default() -> Self { + Self::new() + } +} + +impl LnFunc { + pub fn new() -> Self { + use DataType::*; + Self { + signature: Signature::uniform( + 1, + vec![Float64, Float32], + Volatility::Immutable, + ), + } + } +} + +impl ScalarUDFImpl for LnFunc { + fn name(&self) -> &str { + "ln" + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, arg_types: &[DataType]) -> Result { + match &arg_types[0] { + DataType::Float32 => Ok(DataType::Float32), + _ => Ok(DataType::Float64), + } + } + + fn output_ordering(&self, input: &[ExprProperties]) -> Result { + ln_order(input) + } + + fn evaluate_bounds(&self, inputs: &[&Interval]) -> Result { + unbounded_bounds(inputs) + } + + fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { + let args = ColumnarValue::values_to_arrays(&args.args)?; + let [arg] = take_function_args(self.name(), args)?; + + let arr: ArrayRef = match arg.data_type() { + DataType::Float64 => { + let result: Float64Array = arg + .as_primitive::() + .try_unary(checked_ln_f64)?; + Arc::new(result) as ArrayRef + } + DataType::Float32 => { + let result: Float32Array = arg + .as_primitive::() + .try_unary(checked_ln_f32)?; + Arc::new(result) as ArrayRef + } + other => { + return exec_err!( + "Unsupported data type {other:?} for function {}", + self.name() + ); + } + }; + + Ok(ColumnarValue::Array(arr)) + } + + fn documentation(&self) -> Option<&Documentation> { + Some(get_ln_doc()) + } +} + +fn checked_ln_f64(value: f64) -> Result { + if value < 0.0 { + Err(ArrowError::ComputeError( + "Cannot take logarithm of a negative number".to_string(), + )) + } else { + Ok(value.ln()) + } +} + +fn checked_ln_f32(value: f32) -> Result { + if value < 0.0 { + Err(ArrowError::ComputeError( + "Cannot take logarithm of a negative number".to_string(), + )) + } else { + Ok(value.ln()) + } +} diff --git a/datafusion/functions/src/math/mod.rs b/datafusion/functions/src/math/mod.rs index 610e773d68fd0..80d577ca6f117 100644 --- a/datafusion/functions/src/math/mod.rs +++ b/datafusion/functions/src/math/mod.rs @@ -31,6 +31,7 @@ pub mod floor; pub mod gcd; pub mod iszero; pub mod lcm; +pub mod ln; pub mod log; pub mod monotonicity; pub mod nans; @@ -148,14 +149,7 @@ make_udf_function!(gcd::GcdFunc, gcd); make_udf_function!(nans::IsNanFunc, isnan); make_udf_function!(iszero::IsZeroFunc, iszero); make_udf_function!(lcm::LcmFunc, lcm); -make_math_unary_udf!( - LnFunc, - ln, - ln, - super::ln_order, - super::bounds::unbounded_bounds, - super::get_ln_doc -); +make_udf_function!(ln::LnFunc, ln); make_math_unary_udf!( Log2Func, log2, diff --git a/datafusion/sqllogictest/test_files/scalar.slt b/datafusion/sqllogictest/test_files/scalar.slt index 89ae30e3c047b..feb8e9f3d7db3 100644 --- a/datafusion/sqllogictest/test_files/scalar.slt +++ b/datafusion/sqllogictest/test_files/scalar.slt @@ -581,14 +581,26 @@ select ln(0); ---- -Infinity -# ln with columns (round is needed to normalize the outputs of different operating systems) +# ln with positive column values (round is needed to normalize the outputs of different operating systems) query RRR rowsort -select round(ln(a), 5), round(ln(b), 5), round(ln(c), 5) from signed_integers; +select round(ln(a), 5), round(ln(b), 5), round(ln(c), 5) from unsigned_integers; ---- -0.69315 NaN 4.81218 +0 4.60517 6.34036 +0.69315 6.90776 4.81218 +1.09861 9.21034 6.88551 1.38629 NULL NULL -NaN 4.60517 NaN -NaN 9.21034 NaN + +# ln rejects negative scalar inputs +query error Arrow error: Compute error: Cannot take logarithm of a negative number +select ln((-1.0)::float8); + +# ln rejects negative Float32 scalar inputs +query error Arrow error: Compute error: Cannot take logarithm of a negative number +select ln((-1.0)::float4); + +# ln rejects negative column inputs +query error Arrow error: Compute error: Cannot take logarithm of a negative number +select round(ln(a), 5) from signed_integers; ## log