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