1use std::ops::{Div, Mul};
19use std::sync::Arc;
20
21use crate::utils::{calculate_binary_decimal_math_cast, make_scalar_function};
22
23use arrow::array::{ArrayRef, AsArray, PrimitiveArray};
24use arrow::datatypes::DataType::{
25 Decimal32, Decimal64, Decimal128, Decimal256, Float32, Float64,
26};
27use arrow::datatypes::{
28 ArrowPrimitiveType, DataType, Decimal32Type, Decimal64Type, Decimal128Type,
29 Decimal256Type, DecimalType, Float32Type, Float64Type, Int64Type,
30};
31use datafusion_common::ScalarValue::Int64;
32use datafusion_common::types::{
33 NativeType, logical_float32, logical_float64, logical_int64,
34};
35use datafusion_common::{Result, ScalarValue, exec_err, plan_err};
36use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
37use datafusion_expr::{
38 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
39 Volatility,
40};
41use datafusion_expr_common::signature::{Coercion, TypeSignature, TypeSignatureClass};
42use datafusion_macros::user_doc;
43use num_traits::{Float, NumCast, One, Zero, pow};
44
45#[user_doc(
46 doc_section(label = "Math Functions"),
47 description = "Truncates a number to a whole number or truncated to the specified decimal places.",
48 syntax_example = "trunc(numeric_expression[, decimal_places])",
49 standard_argument(name = "numeric_expression", prefix = "Numeric"),
50 argument(
51 name = "decimal_places",
52 description = r#"Optional. The number of decimal places to
53 truncate to. Defaults to 0 (truncate to a whole number). If
54 `decimal_places` is a positive integer, truncates digits to the
55 right of the decimal point. If `decimal_places` is a negative
56 integer, replaces digits to the left of the decimal point with `0`."#
57 ),
58 sql_example = r#"
59 ```sql
60 > SELECT trunc(42.738);
61 +----------------+
62 | trunc(42.738) |
63 +----------------+
64 | 42 |
65 +----------------+
66 ```"#
67)]
68#[derive(Debug, PartialEq, Eq, Hash)]
69pub struct TruncFunc {
70 signature: Signature,
71}
72
73impl Default for TruncFunc {
74 fn default() -> Self {
75 TruncFunc::new()
76 }
77}
78
79impl TruncFunc {
80 pub fn new() -> Self {
81 let decimal = Coercion::new_exact(TypeSignatureClass::Decimal);
82 let decimal_places = Coercion::new_implicit(
83 TypeSignatureClass::Native(logical_int64()),
84 vec![TypeSignatureClass::Integer],
85 NativeType::Int64,
86 );
87 let float32 = Coercion::new_exact(TypeSignatureClass::Native(logical_float32()));
88 let float64 = Coercion::new_implicit(
89 TypeSignatureClass::Native(logical_float64()),
90 vec![TypeSignatureClass::Numeric],
91 NativeType::Float64,
92 );
93 Self {
94 signature: Signature::one_of(
100 vec![
101 TypeSignature::Coercible(vec![
102 decimal.clone(),
103 decimal_places.clone(),
104 ]),
105 TypeSignature::Coercible(vec![decimal]),
106 TypeSignature::Coercible(vec![
107 float32.clone(),
108 decimal_places.clone(),
109 ]),
110 TypeSignature::Coercible(vec![float32]),
111 TypeSignature::Coercible(vec![float64.clone(), decimal_places]),
112 TypeSignature::Coercible(vec![float64]),
113 ],
114 Volatility::Immutable,
115 ),
116 }
117 }
118}
119
120impl ScalarUDFImpl for TruncFunc {
121 fn name(&self) -> &str {
122 "trunc"
123 }
124
125 fn is_strict(&self) -> bool {
126 true
127 }
128
129 fn signature(&self) -> &Signature {
130 &self.signature
131 }
132
133 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
134 match &arg_types[0] {
135 Float32 => Ok(Float32),
136 Float64 => Ok(Float64),
137 dt if dt.is_decimal() => Ok(dt.clone()),
138 DataType::Null => Ok(Float64),
139 _ => plan_err!(
140 "Unsupported data type {:?} for function {}",
141 arg_types[0],
142 self.name()
143 ),
144 }
145 }
146
147 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
148 let precision = match args.args.get(1) {
150 Some(ColumnarValue::Scalar(Int64(Some(p)))) => Some(*p),
151 Some(ColumnarValue::Scalar(Int64(None))) => None, Some(ColumnarValue::Array(_)) => {
153 return make_scalar_function(trunc, vec![])(&args.args);
155 }
156 None => Some(0), Some(cv) => {
158 return exec_err!(
159 "trunc function requires precision to be Int64, got {:?}",
160 cv.data_type()
161 );
162 }
163 };
164
165 let has_precision_arg = args.args.len() == 2;
170
171 match (&args.args[0], precision) {
173 (ColumnarValue::Scalar(sv), _) if sv.is_null() => {
175 ColumnarValue::Scalar(ScalarValue::Null).cast_to(args.return_type(), None)
176 }
177 (_, None) => {
178 ColumnarValue::Scalar(ScalarValue::Null).cast_to(args.return_type(), None)
179 }
180 (ColumnarValue::Scalar(ScalarValue::Float64(Some(v))), Some(p)) => Ok(
182 ColumnarValue::Scalar(ScalarValue::Float64(Some(if p == 0 {
183 v.trunc()
184 } else {
185 compute_truncate64(*v, p)
186 }))),
187 ),
188 (ColumnarValue::Scalar(ScalarValue::Float32(Some(v))), Some(p)) => Ok(
189 ColumnarValue::Scalar(ScalarValue::Float32(Some(if p == 0 {
190 v.trunc()
191 } else {
192 compute_truncate32(*v, p)
193 }))),
194 ),
195 (
196 ColumnarValue::Scalar(ScalarValue::Decimal32(
197 Some(v),
198 lprecision,
199 lscale,
200 )),
201 Some(p),
202 ) => Ok(ColumnarValue::Scalar(ScalarValue::Decimal32(
203 Some(compute_truncate_decimal::<Decimal32Type>(*v, *lscale, p)),
204 *lprecision,
205 *lscale,
206 ))),
207 (
208 ColumnarValue::Scalar(ScalarValue::Decimal64(
209 Some(v),
210 lprecision,
211 lscale,
212 )),
213 Some(p),
214 ) => Ok(ColumnarValue::Scalar(ScalarValue::Decimal64(
215 Some(compute_truncate_decimal::<Decimal64Type>(*v, *lscale, p)),
216 *lprecision,
217 *lscale,
218 ))),
219 (
220 ColumnarValue::Scalar(ScalarValue::Decimal128(
221 Some(v),
222 lprecision,
223 lscale,
224 )),
225 Some(p),
226 ) => Ok(ColumnarValue::Scalar(ScalarValue::Decimal128(
227 Some(compute_truncate_decimal::<Decimal128Type>(*v, *lscale, p)),
228 *lprecision,
229 *lscale,
230 ))),
231 (
232 ColumnarValue::Scalar(ScalarValue::Decimal256(
233 Some(v),
234 lprecision,
235 lscale,
236 )),
237 Some(p),
238 ) => Ok(ColumnarValue::Scalar(ScalarValue::Decimal256(
239 Some(compute_truncate_decimal::<Decimal256Type>(*v, *lscale, p)),
240 *lprecision,
241 *lscale,
242 ))),
243
244 (ColumnarValue::Array(arr), Some(p))
249 if has_precision_arg && arr.data_type() == &Float64 =>
250 {
251 Ok(ColumnarValue::Array(truncate_float_array::<Float64Type>(
252 arr, p,
253 )))
254 }
255 (ColumnarValue::Array(arr), Some(p))
256 if has_precision_arg && arr.data_type() == &Float32 =>
257 {
258 Ok(ColumnarValue::Array(truncate_float_array::<Float32Type>(
259 arr, p,
260 )))
261 }
262
263 _ => make_scalar_function(trunc, vec![])(&args.args),
265 }
266 }
267
268 fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
269 let value = &input[0];
271 let precision = input.get(1);
272
273 if precision
274 .map(|r| r.sort_properties.eq(&SortProperties::Singleton))
275 .unwrap_or(true)
276 {
277 Ok(value.sort_properties)
278 } else {
279 Ok(SortProperties::Unordered)
280 }
281 }
282
283 fn documentation(&self) -> Option<&Documentation> {
284 self.doc()
285 }
286}
287
288fn trunc(args: &[ArrayRef]) -> Result<ArrayRef> {
290 if args.len() != 1 && args.len() != 2 {
291 return exec_err!(
292 "truncate function requires one or two arguments, got {}",
293 args.len()
294 );
295 }
296
297 let num = &args[0];
300 let precision = if args.len() == 1 {
301 ColumnarValue::Scalar(Int64(Some(0)))
302 } else {
303 ColumnarValue::Array(Arc::clone(&args[1]))
304 };
305
306 match num.data_type() {
307 Float64 => match precision {
308 ColumnarValue::Scalar(Int64(Some(0))) => {
309 Ok(Arc::new(
310 args[0]
311 .as_primitive::<Float64Type>()
312 .unary::<_, Float64Type>(|x: f64| {
313 if x == 0_f64 { 0_f64 } else { x.trunc() }
314 }),
315 ) as ArrayRef)
316 }
317 ColumnarValue::Array(precision) => {
318 let num_array = num.as_primitive::<Float64Type>();
319 let precision_array = precision.as_primitive::<Int64Type>();
320 let result: PrimitiveArray<Float64Type> =
321 arrow::compute::binary(num_array, precision_array, |x, y| {
322 compute_truncate64(x, y)
323 })?;
324
325 Ok(Arc::new(result) as ArrayRef)
326 }
327 _ => exec_err!("trunc function requires a scalar or array for precision"),
328 },
329 Float32 => match precision {
330 ColumnarValue::Scalar(Int64(Some(0))) => {
331 Ok(Arc::new(
332 args[0]
333 .as_primitive::<Float32Type>()
334 .unary::<_, Float32Type>(|x: f32| {
335 if x == 0_f32 { 0_f32 } else { x.trunc() }
336 }),
337 ) as ArrayRef)
338 }
339 ColumnarValue::Array(precision) => {
340 let num_array = num.as_primitive::<Float32Type>();
341 let precision_array = precision.as_primitive::<Int64Type>();
342 let result: PrimitiveArray<Float32Type> =
343 arrow::compute::binary(num_array, precision_array, |x, y| {
344 compute_truncate32(x, y)
345 })?;
346
347 Ok(Arc::new(result) as ArrayRef)
348 }
349 _ => exec_err!("trunc function requires a scalar or array for precision"),
350 },
351 Decimal32(lprecision, lscale) => Ok(calculate_binary_decimal_math_cast::<
352 Decimal32Type,
353 Int64Type,
354 Decimal32Type,
355 _,
356 >(
357 num.as_ref(),
358 &precision,
359 |v, y| Ok(compute_truncate_decimal::<Decimal32Type>(v, *lscale, y)),
360 *lprecision,
361 *lscale,
362 &DataType::Int64,
363 )? as ArrayRef),
364 Decimal64(lprecision, lscale) => Ok(calculate_binary_decimal_math_cast::<
365 Decimal64Type,
366 Int64Type,
367 Decimal64Type,
368 _,
369 >(
370 num.as_ref(),
371 &precision,
372 |v, y| Ok(compute_truncate_decimal::<Decimal64Type>(v, *lscale, y)),
373 *lprecision,
374 *lscale,
375 &DataType::Int64,
376 )? as ArrayRef),
377 Decimal128(lprecision, lscale) => Ok(calculate_binary_decimal_math_cast::<
378 Decimal128Type,
379 Int64Type,
380 Decimal128Type,
381 _,
382 >(
383 num.as_ref(),
384 &precision,
385 |v, y| Ok(compute_truncate_decimal::<Decimal128Type>(v, *lscale, y)),
386 *lprecision,
387 *lscale,
388 &DataType::Int64,
389 )? as ArrayRef),
390 Decimal256(lprecision, lscale) => Ok(calculate_binary_decimal_math_cast::<
391 Decimal256Type,
392 Int64Type,
393 Decimal256Type,
394 _,
395 >(
396 num.as_ref(),
397 &precision,
398 |v, y| Ok(compute_truncate_decimal::<Decimal256Type>(v, *lscale, y)),
399 *lprecision,
400 *lscale,
401 &DataType::Int64,
402 )? as ArrayRef),
403 other => exec_err!("Unsupported data type {other:?} for function trunc"),
404 }
405}
406
407fn truncate_with_factor<F: Float>(x: F, factor: F) -> F {
411 (x * factor).trunc() / factor
412}
413
414fn truncate_float_array<T>(arr: &ArrayRef, precision: i64) -> ArrayRef
417where
418 T: ArrowPrimitiveType,
419 T::Native: Float,
420{
421 let factor = <T::Native as NumCast>::from(10.0_f64)
422 .unwrap()
423 .powi(precision as i32);
424 Arc::new(
425 arr.as_primitive::<T>()
426 .unary::<_, T>(|x| truncate_with_factor(x, factor)),
427 )
428}
429
430fn compute_truncate32(x: f32, y: i64) -> f32 {
431 truncate_with_factor(x, 10.0_f32.powi(y as i32))
432}
433
434fn compute_truncate64(x: f64, y: i64) -> f64 {
435 truncate_with_factor(x, 10.0_f64.powi(y as i32))
436}
437
438fn compute_truncate_decimal<T>(
451 x: T::Native,
452 scale: i8,
453 truncate_precision: i64,
454) -> T::Native
455where
456 T: DecimalType,
457 T::Native: Copy + From<i32> + One + Zero + Div<Output = T::Native> + Mul,
458{
459 let exp = (scale as i64).saturating_sub(truncate_precision);
461 if exp <= 0 {
462 x
464 } else if exp >= T::MAX_PRECISION as i64 {
465 T::Native::zero()
467 } else {
468 let base = T::Native::from(10_i32);
469 let exp = exp as usize;
470 let factor = pow::<T::Native>(base, exp);
471 (x / factor) * factor
473 }
474}
475
476#[cfg(test)]
477mod test {
478 use std::sync::Arc;
479
480 use crate::math::trunc::{compute_truncate_decimal, trunc};
481
482 use arrow::array::{ArrayRef, Float32Array, Float64Array, Int64Array};
483 use arrow::datatypes::Decimal128Type;
484 use datafusion_common::cast::{as_float32_array, as_float64_array};
485
486 #[test]
487 fn test_truncate_32() {
488 let args: Vec<ArrayRef> = vec![
489 Arc::new(Float32Array::from(vec![
490 15.0,
491 1_234.267_8,
492 1_233.123_4,
493 3.312_979_2,
494 -21.123_4,
495 ])),
496 Arc::new(Int64Array::from(vec![0, 3, 2, 5, 6])),
497 ];
498
499 let result = trunc(&args).expect("failed to initialize function truncate");
500 let floats =
501 as_float32_array(&result).expect("failed to initialize function truncate");
502
503 assert_eq!(floats.len(), 5);
504 assert_eq!(floats.value(0), 15.0);
505 assert_eq!(floats.value(1), 1_234.267);
506 assert_eq!(floats.value(2), 1_233.12);
507 assert_eq!(floats.value(3), 3.312_97);
508 assert_eq!(floats.value(4), -21.123_4);
509 }
510
511 #[test]
512 fn test_truncate_64() {
513 let args: Vec<ArrayRef> = vec![
514 Arc::new(Float64Array::from(vec![
515 5.0,
516 234.267_812_176,
517 123.123_456_789,
518 123.312_979_313_2,
519 -321.123_1,
520 ])),
521 Arc::new(Int64Array::from(vec![0, 3, 2, 5, 6])),
522 ];
523
524 let result = trunc(&args).expect("failed to initialize function truncate");
525 let floats =
526 as_float64_array(&result).expect("failed to initialize function truncate");
527
528 assert_eq!(floats.len(), 5);
529 assert_eq!(floats.value(0), 5.0);
530 assert_eq!(floats.value(1), 234.267);
531 assert_eq!(floats.value(2), 123.12);
532 assert_eq!(floats.value(3), 123.312_97);
533 assert_eq!(floats.value(4), -321.123_1);
534 }
535
536 #[test]
537 fn test_truncate_64_one_arg() {
538 let args: Vec<ArrayRef> = vec![Arc::new(Float64Array::from(vec![
539 5.0,
540 234.267_812,
541 123.123_45,
542 123.312_979_313_2,
543 -321.123,
544 ]))];
545
546 let result = trunc(&args).expect("failed to initialize function truncate");
547 let floats =
548 as_float64_array(&result).expect("failed to initialize function truncate");
549
550 assert_eq!(floats.len(), 5);
551 assert_eq!(floats.value(0), 5.0);
552 assert_eq!(floats.value(1), 234.0);
553 assert_eq!(floats.value(2), 123.0);
554 assert_eq!(floats.value(3), 123.0);
555 assert_eq!(floats.value(4), -321.0);
556 }
557
558 #[test]
559 fn test_compute_truncate_decimal128() {
560 assert_eq!(
562 compute_truncate_decimal::<Decimal128Type>(123_456, 4, 3),
563 123_450
564 );
565 assert_eq!(
567 compute_truncate_decimal::<Decimal128Type>(123_456, 4, 1),
568 123_000
569 );
570
571 assert_eq!(
573 compute_truncate_decimal::<Decimal128Type>(123_456, 4, 10),
574 123_456
575 );
576
577 assert_eq!(
579 compute_truncate_decimal::<Decimal128Type>(123_456, 4, 0),
580 120_000
581 );
582
583 assert_eq!(
585 compute_truncate_decimal::<Decimal128Type>(123_456, 4, -1),
586 100_000
587 );
588
589 assert_eq!(
591 compute_truncate_decimal::<Decimal128Type>(123_456, 4, -3),
592 0
593 );
594
595 assert_eq!(
597 compute_truncate_decimal::<Decimal128Type>(123_456, 2, -3),
598 100_000
599 );
600
601 assert_eq!(
603 compute_truncate_decimal::<Decimal128Type>(123_456, 4, -900),
604 0
605 );
606
607 assert_eq!(
609 compute_truncate_decimal::<Decimal128Type>(-123_456, 4, 3),
610 -123_450
611 );
612 }
613}