datafusion_functions/math/
lcm.rs1use std::any::Any;
19use std::sync::Arc;
20
21use arrow::array::{ArrayRef, Int64Array};
22use arrow::datatypes::DataType;
23use arrow::datatypes::DataType::Int64;
24
25use arrow::error::ArrowError;
26use datafusion_common::{
27 arrow_datafusion_err, exec_err, internal_datafusion_err, DataFusionError, Result,
28};
29use datafusion_expr::{
30 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
31 Volatility,
32};
33use datafusion_macros::user_doc;
34
35use super::gcd::unsigned_gcd;
36use crate::utils::make_scalar_function;
37
38#[user_doc(
39 doc_section(label = "Math Functions"),
40 description = "Returns the least common multiple of `expression_x` and `expression_y`. Returns 0 if either input is zero.",
41 syntax_example = "lcm(expression_x, expression_y)",
42 standard_argument(name = "expression_x", prefix = "First numeric"),
43 standard_argument(name = "expression_y", prefix = "Second numeric")
44)]
45#[derive(Debug)]
46pub struct LcmFunc {
47 signature: Signature,
48}
49
50impl Default for LcmFunc {
51 fn default() -> Self {
52 LcmFunc::new()
53 }
54}
55
56impl LcmFunc {
57 pub fn new() -> Self {
58 use DataType::*;
59 Self {
60 signature: Signature::uniform(2, vec![Int64], Volatility::Immutable),
61 }
62 }
63}
64
65impl ScalarUDFImpl for LcmFunc {
66 fn as_any(&self) -> &dyn Any {
67 self
68 }
69
70 fn name(&self) -> &str {
71 "lcm"
72 }
73
74 fn signature(&self) -> &Signature {
75 &self.signature
76 }
77
78 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
79 Ok(Int64)
80 }
81
82 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
83 make_scalar_function(lcm, vec![])(&args.args)
84 }
85
86 fn documentation(&self) -> Option<&Documentation> {
87 self.doc()
88 }
89}
90
91fn lcm(args: &[ArrayRef]) -> Result<ArrayRef> {
93 let compute_lcm = |x: i64, y: i64| {
94 if x == 0 || y == 0 {
95 return Ok(0);
96 }
97
98 let a = x.unsigned_abs();
100 let b = y.unsigned_abs();
101 let gcd = unsigned_gcd(a, b);
102 (a / gcd)
104 .checked_mul(b)
105 .and_then(|v| i64::try_from(v).ok())
106 .ok_or_else(|| {
107 arrow_datafusion_err!(ArrowError::ComputeError(format!(
108 "Signed integer overflow in LCM({x}, {y})"
109 )))
110 })
111 };
112
113 match args[0].data_type() {
114 Int64 => {
115 let arg1 = downcast_named_arg!(&args[0], "x", Int64Array);
116 let arg2 = downcast_named_arg!(&args[1], "y", Int64Array);
117
118 Ok(arg1
119 .iter()
120 .zip(arg2.iter())
121 .map(|(a1, a2)| match (a1, a2) {
122 (Some(a1), Some(a2)) => Ok(Some(compute_lcm(a1, a2)?)),
123 _ => Ok(None),
124 })
125 .collect::<Result<Int64Array>>()
126 .map(Arc::new)? as ArrayRef)
127 }
128 other => exec_err!("Unsupported data type {other:?} for function lcm"),
129 }
130}
131
132#[cfg(test)]
133mod test {
134 use std::sync::Arc;
135
136 use arrow::array::{ArrayRef, Int64Array};
137
138 use datafusion_common::cast::as_int64_array;
139
140 use crate::math::lcm::lcm;
141
142 #[test]
143 fn test_lcm_i64() {
144 let args: Vec<ArrayRef> = vec![
145 Arc::new(Int64Array::from(vec![0, 3, 25, -16])), Arc::new(Int64Array::from(vec![0, -2, 15, 8])), ];
148
149 let result = lcm(&args).expect("failed to initialize function lcm");
150 let ints = as_int64_array(&result).expect("failed to initialize function lcm");
151
152 assert_eq!(ints.len(), 4);
153 assert_eq!(ints.value(0), 0);
154 assert_eq!(ints.value(1), 6);
155 assert_eq!(ints.value(2), 75);
156 assert_eq!(ints.value(3), 16);
157 }
158}