Skip to main content

datafusion_functions/math/
round.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::utils::{calculate_binary_decimal_math_cast, calculate_binary_math};
19
20use arrow::array::{Array, ArrayRef, AsArray};
21use arrow::datatypes::DataType::{
22    Decimal32, Decimal64, Decimal128, Decimal256, Float32, Float64, Int8, Int16, Int32,
23    Int64, UInt8, UInt16, UInt32, UInt64,
24};
25use arrow::datatypes::{
26    ArrowNativeTypeOp, ArrowPrimitiveType, DataType, Decimal32Type, Decimal64Type,
27    Decimal128Type, Decimal256Type, DecimalType, Float32Type, Float64Type, Int8Type,
28    Int16Type, Int32Type, Int64Type, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
29};
30use arrow::datatypes::{Field, FieldRef};
31use arrow::error::ArrowError;
32use datafusion_common::types::{
33    NativeType, logical_float32, logical_float64, logical_int32,
34};
35use datafusion_common::{Result, ScalarValue, exec_err, internal_err};
36use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
37use datafusion_expr::{
38    Coercion, ColumnarValue, Documentation, ReturnFieldArgs, ScalarFunctionArgs,
39    ScalarUDFImpl, Signature, TypeSignature, TypeSignatureClass, Volatility,
40};
41use datafusion_macros::user_doc;
42use num_traits::{PrimInt, Signed, cast, checked_pow};
43use std::sync::Arc;
44
45fn output_scale_for_decimal(precision: u8, input_scale: i8, decimal_places: i32) -> i8 {
46    // `decimal_places` controls the maximum output scale, but scale cannot exceed the input scale.
47    //
48    // For negative-scale decimals, allow further scale reduction to match negative `decimal_places`
49    // (e.g. scale -2 rounded to -3 becomes scale -3). This preserves fixed precision by
50    // representing the rounded result at a coarser scale.
51    if input_scale < 0 {
52        // Decimal scales must be within [-precision, precision] and fit in i8. For negative-scale
53        // decimals, allow rounding to move the output scale further negative, but cap it at
54        // `-precision` (beyond that, the rounded result is always 0).
55        let min_scale = -i32::from(precision);
56        let new_scale = i32::from(input_scale).min(decimal_places).max(min_scale);
57        return new_scale as i8;
58    }
59
60    // The `min` ensures the result is always within i8 range because `input_scale` is i8.
61    let decimal_places = decimal_places.max(0);
62    i32::from(input_scale).min(decimal_places) as i8
63}
64
65fn normalize_decimal_places_for_decimal(
66    decimal_places: i32,
67    precision: u8,
68    scale: i8,
69) -> Option<i32> {
70    if decimal_places >= 0 {
71        return Some(decimal_places);
72    }
73
74    // For fixed precision decimals, the absolute value is strictly less than 10^(precision - scale).
75    // If the rounding position is beyond that (abs(decimal_places) > precision - scale), the
76    // rounded result is always 0, and we can avoid overflow in intermediate 10^n computations.
77    let max_rounding_pow10 = i64::from(precision) - i64::from(scale);
78    if max_rounding_pow10 <= 0 {
79        return None;
80    }
81
82    let abs_decimal_places = i64::from(decimal_places.unsigned_abs());
83    (abs_decimal_places <= max_rounding_pow10).then_some(decimal_places)
84}
85
86fn validate_decimal_precision<T: DecimalType>(
87    value: T::Native,
88    precision: u8,
89    scale: i8,
90) -> Result<T::Native, ArrowError> {
91    T::validate_decimal_precision(value, precision, scale).map_err(|e| {
92        ArrowError::ComputeError(format!(
93            "Decimal overflow: rounded value exceeds precision {precision}: {e}"
94        ))
95    })?;
96    Ok(value)
97}
98
99fn calculate_new_precision_scale<T: DecimalType>(
100    precision: u8,
101    scale: i8,
102    decimal_places: Option<i32>,
103) -> Result<DataType> {
104    if let Some(decimal_places) = decimal_places {
105        let new_scale = output_scale_for_decimal(precision, scale, decimal_places);
106
107        // When rounding an integer decimal (scale == 0) to a negative `decimal_places`, a carry can
108        // add an extra digit to the integer part (e.g. 99 -> 100 when rounding to -1). This can
109        // only happen when the rounding position is within the existing precision.
110        let abs_decimal_places = decimal_places.unsigned_abs();
111        let new_precision = if scale == 0
112            && decimal_places < 0
113            && abs_decimal_places <= u32::from(precision)
114        {
115            precision.saturating_add(1).min(T::MAX_PRECISION)
116        } else {
117            precision
118        };
119        Ok(T::TYPE_CONSTRUCTOR(new_precision, new_scale))
120    } else {
121        let new_precision = precision.saturating_add(1).min(T::MAX_PRECISION);
122        Ok(T::TYPE_CONSTRUCTOR(new_precision, scale))
123    }
124}
125
126fn decimal_places_from_scalar(scalar: &ScalarValue) -> Result<i32> {
127    let out_of_range = |value: String| {
128        datafusion_common::DataFusionError::Execution(format!(
129            "round decimal_places {value} is out of supported i32 range"
130        ))
131    };
132    match scalar {
133        ScalarValue::Int8(Some(v)) => Ok(i32::from(*v)),
134        ScalarValue::Int16(Some(v)) => Ok(i32::from(*v)),
135        ScalarValue::Int32(Some(v)) => Ok(*v),
136        ScalarValue::Int64(Some(v)) => {
137            i32::try_from(*v).map_err(|_| out_of_range(v.to_string()))
138        }
139        ScalarValue::UInt8(Some(v)) => Ok(i32::from(*v)),
140        ScalarValue::UInt16(Some(v)) => Ok(i32::from(*v)),
141        ScalarValue::UInt32(Some(v)) => {
142            i32::try_from(*v).map_err(|_| out_of_range(v.to_string()))
143        }
144        ScalarValue::UInt64(Some(v)) => {
145            i32::try_from(*v).map_err(|_| out_of_range(v.to_string()))
146        }
147        other => exec_err!(
148            "Unexpected datatype for decimal_places: {}",
149            other.data_type()
150        ),
151    }
152}
153
154#[user_doc(
155    doc_section(label = "Math Functions"),
156    description = "Rounds a number to the nearest integer.",
157    syntax_example = "round(numeric_expression[, decimal_places])",
158    standard_argument(name = "numeric_expression", prefix = "Numeric"),
159    argument(
160        name = "decimal_places",
161        description = "Optional. The number of decimal places to round to. Defaults to 0."
162    ),
163    sql_example = r#"```sql
164> SELECT round(3.14159);
165+--------------+
166| round(3.14159)|
167+--------------+
168| 3.0          |
169+--------------+
170```"#
171)]
172#[derive(Debug, PartialEq, Eq, Hash)]
173pub struct RoundFunc {
174    signature: Signature,
175}
176
177impl Default for RoundFunc {
178    fn default() -> Self {
179        RoundFunc::new()
180    }
181}
182
183impl RoundFunc {
184    pub fn new() -> Self {
185        let decimal = Coercion::new_exact(TypeSignatureClass::Decimal);
186        let decimal_places = Coercion::new_implicit(
187            TypeSignatureClass::Native(logical_int32()),
188            vec![TypeSignatureClass::Integer],
189            NativeType::Int32,
190        );
191        let integer = Coercion::new_exact(TypeSignatureClass::Integer);
192        let float32 = Coercion::new_exact(TypeSignatureClass::Native(logical_float32()));
193        let float64 = Coercion::new_implicit(
194            TypeSignatureClass::Native(logical_float64()),
195            vec![TypeSignatureClass::Numeric],
196            NativeType::Float64,
197        );
198        Self {
199            signature: Signature::one_of(
200                vec![
201                    TypeSignature::Coercible(vec![
202                        decimal.clone(),
203                        decimal_places.clone(),
204                    ]),
205                    TypeSignature::Coercible(vec![decimal]),
206                    TypeSignature::Coercible(vec![
207                        integer.clone(),
208                        decimal_places.clone(),
209                    ]),
210                    TypeSignature::Coercible(vec![integer]),
211                    TypeSignature::Coercible(vec![
212                        float32.clone(),
213                        decimal_places.clone(),
214                    ]),
215                    TypeSignature::Coercible(vec![float32]),
216                    TypeSignature::Coercible(vec![float64.clone(), decimal_places]),
217                    TypeSignature::Coercible(vec![float64]),
218                ],
219                Volatility::Immutable,
220            ),
221        }
222    }
223}
224
225impl ScalarUDFImpl for RoundFunc {
226    fn name(&self) -> &str {
227        "round"
228    }
229
230    fn is_strict(&self) -> bool {
231        true
232    }
233
234    fn signature(&self) -> &Signature {
235        &self.signature
236    }
237
238    fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
239        let input_field = &args.arg_fields[0];
240        let input_type = input_field.data_type();
241
242        // If decimal_places is a scalar literal, we can incorporate it into the output type
243        // (scale reduction). Otherwise, keep the input scale as we can't pick a per-row scale.
244        //
245        // Note: `scalar_arguments` contains the original literal values (pre-coercion), so
246        // integer literals may appear as Int64 even though the signature coerces them to Int32.
247        let decimal_places: Option<i32> = match args.scalar_arguments.get(1) {
248            None => Some(0),    // No dp argument means default to 0
249            Some(None) => None, // dp is not a literal (e.g. column)
250            Some(Some(scalar)) if scalar.is_null() => Some(0), // null dp => default to 0
251            Some(Some(scalar)) => Some(decimal_places_from_scalar(scalar)?),
252        };
253
254        // Calculate return type based on input type
255        // For decimals: reduce scale to decimal_places (reclaims precision for integer part)
256        // This matches Spark/DuckDB behavior where ROUND adjusts the scale
257        // BUT only if dp is a scalar literal - otherwise keep original scale and add
258        // extra precision to accommodate potential carry-over.
259        let return_type =
260            match input_type {
261                input_type if input_type.is_integer() => input_type.clone(),
262                Float32 => Float32,
263                Decimal32(precision, scale) => calculate_new_precision_scale::<
264                    Decimal32Type,
265                >(
266                    *precision, *scale, decimal_places
267                )?,
268                Decimal64(precision, scale) => calculate_new_precision_scale::<
269                    Decimal64Type,
270                >(
271                    *precision, *scale, decimal_places
272                )?,
273                Decimal128(precision, scale) => calculate_new_precision_scale::<
274                    Decimal128Type,
275                >(
276                    *precision, *scale, decimal_places
277                )?,
278                Decimal256(precision, scale) => calculate_new_precision_scale::<
279                    Decimal256Type,
280                >(
281                    *precision, *scale, decimal_places
282                )?,
283                _ => Float64,
284            };
285
286        let nullable = args.arg_fields.iter().any(|f| f.is_nullable());
287        Ok(Arc::new(Field::new(self.name(), return_type, nullable)))
288    }
289
290    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
291        internal_err!("use return_field_from_args instead")
292    }
293
294    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
295        if args.arg_fields.iter().any(|a| a.data_type().is_null()) {
296            return ColumnarValue::Scalar(ScalarValue::Null)
297                .cast_to(args.return_type(), None);
298        }
299
300        let default_decimal_places = ColumnarValue::Scalar(ScalarValue::Int32(Some(0)));
301        let decimal_places = if args.args.len() == 2 {
302            &args.args[1]
303        } else {
304            &default_decimal_places
305        };
306
307        if let (ColumnarValue::Scalar(value_scalar), ColumnarValue::Scalar(dp_scalar)) =
308            (&args.args[0], decimal_places)
309        {
310            if value_scalar.is_null() || dp_scalar.is_null() {
311                return ColumnarValue::Scalar(ScalarValue::Null)
312                    .cast_to(args.return_type(), None);
313            }
314
315            let dp = if let ScalarValue::Int32(Some(dp)) = dp_scalar {
316                *dp
317            } else {
318                return internal_err!(
319                    "Unexpected datatype for decimal_places: {}",
320                    dp_scalar.data_type()
321                );
322            };
323
324            match (value_scalar, args.return_type()) {
325                (value_scalar, return_type) if return_type.is_integer() => {
326                    round_integer_scalar(value_scalar, return_type, dp)
327                }
328                (ScalarValue::Float32(Some(v)), _) => {
329                    let rounded = round_float(*v, dp)?;
330                    Ok(ColumnarValue::Scalar(ScalarValue::from(rounded)))
331                }
332                (ScalarValue::Float64(Some(v)), _) => {
333                    let rounded = round_float(*v, dp)?;
334                    Ok(ColumnarValue::Scalar(ScalarValue::from(rounded)))
335                }
336                (
337                    ScalarValue::Decimal32(Some(v), in_precision, scale),
338                    Decimal32(out_precision, out_scale),
339                ) => {
340                    let rounded =
341                        round_decimal_or_zero(*v, *in_precision, *scale, *out_scale, dp)?;
342                    let rounded = if *out_precision == Decimal32Type::MAX_PRECISION
343                        && *scale == 0
344                        && dp < 0
345                    {
346                        // With scale == 0 and negative dp, rounding can carry into an additional
347                        // digit (e.g. 99 -> 100). If we're already at max precision we can't widen
348                        // the type, so validate and error rather than producing an invalid decimal.
349                        validate_decimal_precision::<Decimal32Type>(
350                            rounded,
351                            *out_precision,
352                            *out_scale,
353                        )
354                    } else {
355                        Ok(rounded)
356                    }?;
357                    let scalar =
358                        ScalarValue::Decimal32(Some(rounded), *out_precision, *out_scale);
359                    Ok(ColumnarValue::Scalar(scalar))
360                }
361                (
362                    ScalarValue::Decimal64(Some(v), in_precision, scale),
363                    Decimal64(out_precision, out_scale),
364                ) => {
365                    let rounded =
366                        round_decimal_or_zero(*v, *in_precision, *scale, *out_scale, dp)?;
367                    let rounded = if *out_precision == Decimal64Type::MAX_PRECISION
368                        && *scale == 0
369                        && dp < 0
370                    {
371                        // See Decimal32 branch for details.
372                        validate_decimal_precision::<Decimal64Type>(
373                            rounded,
374                            *out_precision,
375                            *out_scale,
376                        )
377                    } else {
378                        Ok(rounded)
379                    }?;
380                    let scalar =
381                        ScalarValue::Decimal64(Some(rounded), *out_precision, *out_scale);
382                    Ok(ColumnarValue::Scalar(scalar))
383                }
384                (
385                    ScalarValue::Decimal128(Some(v), in_precision, scale),
386                    Decimal128(out_precision, out_scale),
387                ) => {
388                    let rounded =
389                        round_decimal_or_zero(*v, *in_precision, *scale, *out_scale, dp)?;
390                    let rounded = if *out_precision == Decimal128Type::MAX_PRECISION
391                        && *scale == 0
392                        && dp < 0
393                    {
394                        // See Decimal32 branch for details.
395                        validate_decimal_precision::<Decimal128Type>(
396                            rounded,
397                            *out_precision,
398                            *out_scale,
399                        )
400                    } else {
401                        Ok(rounded)
402                    }?;
403                    let scalar = ScalarValue::Decimal128(
404                        Some(rounded),
405                        *out_precision,
406                        *out_scale,
407                    );
408                    Ok(ColumnarValue::Scalar(scalar))
409                }
410                (
411                    ScalarValue::Decimal256(Some(v), in_precision, scale),
412                    Decimal256(out_precision, out_scale),
413                ) => {
414                    let rounded =
415                        round_decimal_or_zero(*v, *in_precision, *scale, *out_scale, dp)?;
416                    let rounded = if *out_precision == Decimal256Type::MAX_PRECISION
417                        && *scale == 0
418                        && dp < 0
419                    {
420                        // See Decimal32 branch for details.
421                        validate_decimal_precision::<Decimal256Type>(
422                            rounded,
423                            *out_precision,
424                            *out_scale,
425                        )
426                    } else {
427                        Ok(rounded)
428                    }?;
429                    let scalar = ScalarValue::Decimal256(
430                        Some(rounded),
431                        *out_precision,
432                        *out_scale,
433                    );
434                    Ok(ColumnarValue::Scalar(scalar))
435                }
436                (ScalarValue::Null, _) => ColumnarValue::Scalar(ScalarValue::Null)
437                    .cast_to(args.return_type(), None),
438                (value_scalar, return_type) => {
439                    internal_err!(
440                        "Unexpected datatype for round(value, decimal_places): value {}, return type {}",
441                        value_scalar.data_type(),
442                        return_type
443                    )
444                }
445            }
446        } else {
447            round_columnar(
448                &args.args[0],
449                decimal_places,
450                args.number_rows,
451                args.return_type(),
452            )
453        }
454    }
455
456    fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
457        // round preserves the order of the first argument
458        let value = &input[0];
459        let precision = input.get(1);
460
461        if precision
462            .map(|r| r.sort_properties.eq(&SortProperties::Singleton))
463            .unwrap_or(true)
464        {
465            Ok(value.sort_properties)
466        } else {
467            Ok(SortProperties::Unordered)
468        }
469    }
470
471    fn documentation(&self) -> Option<&Documentation> {
472        self.doc()
473    }
474}
475
476fn round_columnar(
477    value: &ColumnarValue,
478    decimal_places: &ColumnarValue,
479    number_rows: usize,
480    return_type: &DataType,
481) -> Result<ColumnarValue> {
482    let value_array = value.to_array(number_rows)?;
483    let both_scalars = matches!(value, ColumnarValue::Scalar(_))
484        && matches!(decimal_places, ColumnarValue::Scalar(_));
485    let decimal_places_is_array = matches!(decimal_places, ColumnarValue::Array(_));
486
487    let arr: ArrayRef = match (value_array.data_type(), return_type) {
488        (input_type, return_type)
489            if input_type == return_type && return_type.is_integer() =>
490        {
491            match decimal_places {
492                ColumnarValue::Scalar(ScalarValue::Int32(Some(dp))) if *dp >= 0 => {
493                    value_array
494                }
495                _ => round_integer_array(
496                    value_array.as_ref(),
497                    decimal_places,
498                    return_type,
499                )?,
500            }
501        }
502        (Float64, _) => round_float_column::<Float64Type>(&value_array, decimal_places)?,
503        (Float32, _) => round_float_column::<Float32Type>(&value_array, decimal_places)?,
504        (Decimal32(input_precision, scale), Decimal32(precision, new_scale)) => {
505            // reduce scale to reclaim integer precision
506            let result = calculate_binary_decimal_math_cast::<
507                Decimal32Type,
508                Int32Type,
509                Decimal32Type,
510                _,
511            >(
512                value_array.as_ref(),
513                decimal_places,
514                |v, dp| {
515                    let rounded = round_decimal_or_zero(
516                        v,
517                        *input_precision,
518                        *scale,
519                        *new_scale,
520                        dp,
521                    )?;
522                    if *precision == Decimal32Type::MAX_PRECISION
523                        && (decimal_places_is_array || (*scale == 0 && dp < 0))
524                    {
525                        // If we're already at max precision, we can't widen the result type. For
526                        // dp arrays, or for scale == 0 with negative dp, rounding can overflow the
527                        // fixed-precision type. Validate per-row and return an error instead of
528                        // producing an invalid decimal that Arrow may display incorrectly.
529                        validate_decimal_precision::<Decimal32Type>(
530                            rounded, *precision, *new_scale,
531                        )
532                    } else {
533                        Ok(rounded)
534                    }
535                },
536                *precision,
537                *new_scale,
538                &Int32,
539            )?;
540            result as _
541        }
542        (Decimal64(input_precision, scale), Decimal64(precision, new_scale)) => {
543            let result = calculate_binary_decimal_math_cast::<
544                Decimal64Type,
545                Int32Type,
546                Decimal64Type,
547                _,
548            >(
549                value_array.as_ref(),
550                decimal_places,
551                |v, dp| {
552                    let rounded = round_decimal_or_zero(
553                        v,
554                        *input_precision,
555                        *scale,
556                        *new_scale,
557                        dp,
558                    )?;
559                    if *precision == Decimal64Type::MAX_PRECISION
560                        && (decimal_places_is_array || (*scale == 0 && dp < 0))
561                    {
562                        // See Decimal32 branch for details.
563                        validate_decimal_precision::<Decimal64Type>(
564                            rounded, *precision, *new_scale,
565                        )
566                    } else {
567                        Ok(rounded)
568                    }
569                },
570                *precision,
571                *new_scale,
572                &Int32,
573            )?;
574            result as _
575        }
576        (Decimal128(input_precision, scale), Decimal128(precision, new_scale)) => {
577            let result = calculate_binary_decimal_math_cast::<
578                Decimal128Type,
579                Int32Type,
580                Decimal128Type,
581                _,
582            >(
583                value_array.as_ref(),
584                decimal_places,
585                |v, dp| {
586                    let rounded = round_decimal_or_zero(
587                        v,
588                        *input_precision,
589                        *scale,
590                        *new_scale,
591                        dp,
592                    )?;
593                    if *precision == Decimal128Type::MAX_PRECISION
594                        && (decimal_places_is_array || (*scale == 0 && dp < 0))
595                    {
596                        // See Decimal32 branch for details.
597                        validate_decimal_precision::<Decimal128Type>(
598                            rounded, *precision, *new_scale,
599                        )
600                    } else {
601                        Ok(rounded)
602                    }
603                },
604                *precision,
605                *new_scale,
606                &Int32,
607            )?;
608            result as _
609        }
610        (Decimal256(input_precision, scale), Decimal256(precision, new_scale)) => {
611            let result = calculate_binary_decimal_math_cast::<
612                Decimal256Type,
613                Int32Type,
614                Decimal256Type,
615                _,
616            >(
617                value_array.as_ref(),
618                decimal_places,
619                |v, dp| {
620                    let rounded = round_decimal_or_zero(
621                        v,
622                        *input_precision,
623                        *scale,
624                        *new_scale,
625                        dp,
626                    )?;
627                    if *precision == Decimal256Type::MAX_PRECISION
628                        && (decimal_places_is_array || (*scale == 0 && dp < 0))
629                    {
630                        // See Decimal32 branch for details.
631                        validate_decimal_precision::<Decimal256Type>(
632                            rounded, *precision, *new_scale,
633                        )
634                    } else {
635                        Ok(rounded)
636                    }
637                },
638                *precision,
639                *new_scale,
640                &Int32,
641            )?;
642            result as _
643        }
644        (other, _) => exec_err!("Unsupported data type {other:?} for function round")?,
645    };
646
647    if both_scalars {
648        ScalarValue::try_from_array(&arr, 0).map(ColumnarValue::Scalar)
649    } else {
650        Ok(ColumnarValue::Array(arr))
651    }
652}
653
654fn round_signed_integer<T>(
655    value: T,
656    decimal_places: i32,
657    type_name: &str,
658) -> Result<T, ArrowError>
659where
660    T: PrimInt + Signed,
661{
662    if decimal_places >= 0 || value == T::zero() {
663        return Ok(value);
664    }
665
666    let ten = cast::<_, T>(10).expect("10 fits in all integer types");
667    let Some(factor) = checked_pow(ten, decimal_places.unsigned_abs() as usize) else {
668        return Ok(T::zero());
669    };
670
671    let two = cast::<_, T>(2).expect("2 fits in all integer types");
672    let one = T::one();
673    let threshold = factor / two;
674    let mut quotient = value / factor;
675    let remainder = value % factor;
676
677    if remainder >= threshold {
678        quotient = quotient.checked_add(&one).ok_or_else(|| {
679            ArrowError::ComputeError(format!("Overflow while rounding {type_name}"))
680        })?;
681    } else if remainder <= -threshold {
682        quotient = quotient.checked_sub(&one).ok_or_else(|| {
683            ArrowError::ComputeError(format!("Overflow while rounding {type_name}"))
684        })?;
685    }
686
687    quotient.checked_mul(&factor).ok_or_else(|| {
688        ArrowError::ComputeError(format!("Overflow while rounding {type_name}"))
689    })
690}
691
692fn round_unsigned_integer<T>(
693    value: T,
694    decimal_places: i32,
695    type_name: &str,
696) -> Result<T, ArrowError>
697where
698    T: PrimInt,
699{
700    if decimal_places >= 0 || value == T::zero() {
701        return Ok(value);
702    }
703
704    let ten = cast::<_, T>(10).expect("10 fits in all integer types");
705    let Some(factor) = checked_pow(ten, decimal_places.unsigned_abs() as usize) else {
706        return Ok(T::zero());
707    };
708
709    let two = cast::<_, T>(2).expect("2 fits in all integer types");
710    let one = T::one();
711    let threshold = factor / two;
712    let mut quotient = value / factor;
713    let remainder = value % factor;
714
715    if remainder >= threshold {
716        quotient = quotient.checked_add(&one).ok_or_else(|| {
717            ArrowError::ComputeError(format!("Overflow while rounding {type_name}"))
718        })?;
719    }
720
721    quotient.checked_mul(&factor).ok_or_else(|| {
722        ArrowError::ComputeError(format!("Overflow while rounding {type_name}"))
723    })
724}
725
726fn round_integer_scalar(
727    value: &ScalarValue,
728    return_type: &DataType,
729    decimal_places: i32,
730) -> Result<ColumnarValue> {
731    match (value, return_type) {
732        (ScalarValue::Int8(Some(v)), Int8) => Ok(ColumnarValue::Scalar(
733            ScalarValue::Int8(Some(round_signed_integer(*v, decimal_places, "Int8")?)),
734        )),
735        (ScalarValue::Int16(Some(v)), Int16) => Ok(ColumnarValue::Scalar(
736            ScalarValue::Int16(Some(round_signed_integer(*v, decimal_places, "Int16")?)),
737        )),
738        (ScalarValue::Int32(Some(v)), Int32) => Ok(ColumnarValue::Scalar(
739            ScalarValue::Int32(Some(round_signed_integer(*v, decimal_places, "Int32")?)),
740        )),
741        (ScalarValue::Int64(Some(v)), Int64) => Ok(ColumnarValue::Scalar(
742            ScalarValue::Int64(Some(round_signed_integer(*v, decimal_places, "Int64")?)),
743        )),
744        (ScalarValue::UInt8(Some(v)), UInt8) => {
745            Ok(ColumnarValue::Scalar(ScalarValue::UInt8(Some(
746                round_unsigned_integer(*v, decimal_places, "UInt8")?,
747            ))))
748        }
749        (ScalarValue::UInt16(Some(v)), UInt16) => {
750            Ok(ColumnarValue::Scalar(ScalarValue::UInt16(Some(
751                round_unsigned_integer(*v, decimal_places, "UInt16")?,
752            ))))
753        }
754        (ScalarValue::UInt32(Some(v)), UInt32) => {
755            Ok(ColumnarValue::Scalar(ScalarValue::UInt32(Some(
756                round_unsigned_integer(*v, decimal_places, "UInt32")?,
757            ))))
758        }
759        (ScalarValue::UInt64(Some(v)), UInt64) => {
760            Ok(ColumnarValue::Scalar(ScalarValue::UInt64(Some(
761                round_unsigned_integer(*v, decimal_places, "UInt64")?,
762            ))))
763        }
764        _ => internal_err!(
765            "Unexpected integer round input/output types: {} -> {}",
766            value.data_type(),
767            return_type
768        ),
769    }
770}
771
772macro_rules! round_integer_array {
773    ($ARRAY:expr, $DP:expr, $ARRAY_TYPE:ty, $ROUND_FN:ident, $TYPE_NAME:expr) => {{
774        let array = $ARRAY.as_primitive::<$ARRAY_TYPE>();
775
776        let result = calculate_binary_math::<$ARRAY_TYPE, Int32Type, $ARRAY_TYPE, _>(
777            array,
778            $DP,
779            |v, dp| $ROUND_FN(v, dp, $TYPE_NAME),
780        )?;
781
782        Ok(result as ArrayRef)
783    }};
784}
785
786fn round_integer_array(
787    value_array: &dyn Array,
788    decimal_places: &ColumnarValue,
789    return_type: &DataType,
790) -> Result<ArrayRef> {
791    match return_type {
792        Int8 => round_integer_array!(
793            value_array,
794            decimal_places,
795            Int8Type,
796            round_signed_integer,
797            "Int8"
798        ),
799        Int16 => round_integer_array!(
800            value_array,
801            decimal_places,
802            Int16Type,
803            round_signed_integer,
804            "Int16"
805        ),
806        Int32 => round_integer_array!(
807            value_array,
808            decimal_places,
809            Int32Type,
810            round_signed_integer,
811            "Int32"
812        ),
813        Int64 => round_integer_array!(
814            value_array,
815            decimal_places,
816            Int64Type,
817            round_signed_integer,
818            "Int64"
819        ),
820        UInt8 => round_integer_array!(
821            value_array,
822            decimal_places,
823            UInt8Type,
824            round_unsigned_integer,
825            "UInt8"
826        ),
827        UInt16 => round_integer_array!(
828            value_array,
829            decimal_places,
830            UInt16Type,
831            round_unsigned_integer,
832            "UInt16"
833        ),
834        UInt32 => round_integer_array!(
835            value_array,
836            decimal_places,
837            UInt32Type,
838            round_unsigned_integer,
839            "UInt32"
840        ),
841        UInt64 => round_integer_array!(
842            value_array,
843            decimal_places,
844            UInt64Type,
845            round_unsigned_integer,
846            "UInt64"
847        ),
848        _ => internal_err!("Unexpected return type for integer round: {return_type}"),
849    }
850}
851
852/// Rounds a float array to `decimal_places`.
853///
854/// The shared `calculate_binary_math` kernel routes through `try_unary` and
855/// re-evaluates `round_float` (including `10f64.powi(decimal_places)` and a
856/// `Result` check) for every element. When `decimal_places` is a non-null
857/// scalar, the scaling factor can instead be hoisted out of the loop and the
858/// infallible `unary` kernel used, which the compiler can autovectorize.
859/// `unary` also computes over null slots, but it carries the input null buffer
860/// through to the output, so those values stay masked.
861fn round_float_column<PT>(
862    value_array: &ArrayRef,
863    decimal_places: &ColumnarValue,
864) -> Result<ArrayRef>
865where
866    PT: ArrowPrimitiveType,
867    PT::Native: num_traits::Float,
868{
869    // Bring `Float` into scope so `.round()` resolves on the `PT::Native`
870    // projection below.
871    use num_traits::Float;
872
873    if let ColumnarValue::Scalar(ScalarValue::Int32(Some(decimal_places))) =
874        decimal_places
875    {
876        let factor = round_factor::<PT::Native>(*decimal_places)?;
877        let result = value_array
878            .as_primitive::<PT>()
879            .unary::<_, PT>(|value| (value * factor).round() / factor);
880        return Ok(Arc::new(result) as ArrayRef);
881    }
882
883    let result = calculate_binary_math::<PT, Int32Type, PT, _>(
884        value_array.as_ref(),
885        decimal_places,
886        round_float::<PT::Native>,
887    )?;
888    Ok(result as _)
889}
890
891/// Computes the power-of-ten scaling factor used to round to `decimal_places`.
892fn round_factor<T: num_traits::Float>(decimal_places: i32) -> Result<T, ArrowError> {
893    T::from(10_f64.powi(decimal_places)).ok_or_else(|| {
894        ArrowError::ComputeError(format!(
895            "Invalid value for decimal places: {decimal_places}"
896        ))
897    })
898}
899
900fn round_float<T>(value: T, decimal_places: i32) -> Result<T, ArrowError>
901where
902    T: num_traits::Float,
903{
904    let factor = round_factor::<T>(decimal_places)?;
905    Ok((value * factor).round() / factor)
906}
907
908fn round_decimal<V: ArrowNativeTypeOp>(
909    value: V,
910    input_scale: i8,
911    output_scale: i8,
912    decimal_places: i32,
913) -> Result<V, ArrowError> {
914    let diff = i64::from(input_scale) - i64::from(decimal_places);
915    if diff <= 0 {
916        return Ok(value);
917    }
918
919    debug_assert!(diff <= i64::from(u32::MAX));
920    let diff = diff as u32;
921
922    let one = V::ONE;
923    let two = V::from_usize(2).ok_or_else(|| {
924        ArrowError::ComputeError("Internal error: could not create constant 2".into())
925    })?;
926    let ten = V::from_usize(10).ok_or_else(|| {
927        ArrowError::ComputeError("Internal error: could not create constant 10".into())
928    })?;
929
930    let factor = ten.pow_checked(diff).map_err(|_| {
931        ArrowError::ComputeError(format!(
932            "Overflow while rounding decimal with scale {input_scale} and decimal places {decimal_places}"
933        ))
934    })?;
935
936    let mut quotient = value.div_wrapping(factor);
937    let remainder = value.mod_wrapping(factor);
938
939    // `factor` is an even number (10^n, n > 0), so `factor / 2` is the tie threshold
940    let threshold = factor.div_wrapping(two);
941    if remainder >= threshold {
942        quotient = quotient.add_checked(one).map_err(|_| {
943            ArrowError::ComputeError("Overflow while rounding decimal".into())
944        })?;
945    } else if remainder <= threshold.neg_wrapping() {
946        quotient = quotient.sub_checked(one).map_err(|_| {
947            ArrowError::ComputeError("Overflow while rounding decimal".into())
948        })?;
949    }
950
951    // `quotient` is the rounded value at scale `decimal_places`. Rescale to the desired
952    // `output_scale` (which is always >= `decimal_places` in cases where diff > 0).
953    let scale_shift = i64::from(output_scale) - i64::from(decimal_places);
954    if scale_shift == 0 {
955        return Ok(quotient);
956    }
957
958    debug_assert!(scale_shift > 0);
959    debug_assert!(scale_shift <= i64::from(u32::MAX));
960    let scale_shift = scale_shift as u32;
961    let shift_factor = ten.pow_checked(scale_shift).map_err(|_| {
962        ArrowError::ComputeError(format!(
963            "Overflow while rounding decimal with scale {input_scale} and decimal places {decimal_places}"
964        ))
965    })?;
966    quotient
967        .mul_checked(shift_factor)
968        .map_err(|_| ArrowError::ComputeError("Overflow while rounding decimal".into()))
969}
970
971fn round_decimal_or_zero<V: ArrowNativeTypeOp>(
972    value: V,
973    precision: u8,
974    input_scale: i8,
975    output_scale: i8,
976    decimal_places: i32,
977) -> Result<V, ArrowError> {
978    if let Some(dp) =
979        normalize_decimal_places_for_decimal(decimal_places, precision, input_scale)
980    {
981        round_decimal(value, input_scale, output_scale, dp)
982    } else {
983        V::from_usize(0).ok_or_else(|| {
984            ArrowError::ComputeError("Internal error: could not create constant 0".into())
985        })
986    }
987}
988
989#[cfg(test)]
990mod test {
991    use std::sync::Arc;
992
993    use arrow::array::{ArrayRef, Float32Array, Float64Array, Int64Array};
994    use arrow::datatypes::DataType;
995    use datafusion_common::DataFusionError;
996    use datafusion_common::ScalarValue;
997    use datafusion_common::cast::{as_float32_array, as_float64_array};
998    use datafusion_expr::ColumnarValue;
999
1000    fn round_arrays(
1001        value: ArrayRef,
1002        decimal_places: Option<ArrayRef>,
1003    ) -> Result<ArrayRef, DataFusionError> {
1004        let number_rows = value.len();
1005        // NOTE: For decimal inputs, the actual ROUND return type can differ from the
1006        // input type (scale reduction for literal `decimal_places`). These unit tests
1007        // only exercise Float32/Float64 behavior.
1008        let return_type = value.data_type().clone();
1009        let value = ColumnarValue::Array(value);
1010        let decimal_places = decimal_places
1011            .map(ColumnarValue::Array)
1012            .unwrap_or_else(|| ColumnarValue::Scalar(ScalarValue::Int32(Some(0))));
1013
1014        let result =
1015            super::round_columnar(&value, &decimal_places, number_rows, &return_type)?;
1016        match result {
1017            ColumnarValue::Array(array) => Ok(array),
1018            ColumnarValue::Scalar(scalar) => scalar.to_array_of_size(1),
1019        }
1020    }
1021
1022    #[test]
1023    fn test_round_f32() {
1024        let args: Vec<ArrayRef> = vec![
1025            Arc::new(Float32Array::from(vec![125.2345; 10])), // input
1026            Arc::new(Int64Array::from(vec![0, 1, 2, 3, 4, 5, -1, -2, -3, -4])), // decimal_places
1027        ];
1028
1029        let result = round_arrays(Arc::clone(&args[0]), Some(Arc::clone(&args[1])))
1030            .expect("failed to initialize function round");
1031        let floats =
1032            as_float32_array(&result).expect("failed to initialize function round");
1033
1034        let expected = Float32Array::from(vec![
1035            125.0, 125.2, 125.23, 125.235, 125.2345, 125.2345, 130.0, 100.0, 0.0, 0.0,
1036        ]);
1037
1038        assert_eq!(floats, &expected);
1039    }
1040
1041    #[test]
1042    fn test_round_f64() {
1043        let args: Vec<ArrayRef> = vec![
1044            Arc::new(Float64Array::from(vec![125.2345; 10])), // input
1045            Arc::new(Int64Array::from(vec![0, 1, 2, 3, 4, 5, -1, -2, -3, -4])), // decimal_places
1046        ];
1047
1048        let result = round_arrays(Arc::clone(&args[0]), Some(Arc::clone(&args[1])))
1049            .expect("failed to initialize function round");
1050        let floats =
1051            as_float64_array(&result).expect("failed to initialize function round");
1052
1053        let expected = Float64Array::from(vec![
1054            125.0, 125.2, 125.23, 125.235, 125.2345, 125.2345, 130.0, 100.0, 0.0, 0.0,
1055        ]);
1056
1057        assert_eq!(floats, &expected);
1058    }
1059
1060    /// A scalar `decimal_places` takes the hoisted-factor `unary` path, which
1061    /// computes over null slots as well. The nulls must survive into the output.
1062    #[test]
1063    fn test_round_f64_scalar_decimal_places_preserves_nulls() {
1064        let value: ArrayRef = Arc::new(Float64Array::from(vec![
1065            Some(125.2345),
1066            None,
1067            Some(-1.555),
1068            None,
1069        ]));
1070
1071        let result = super::round_columnar(
1072            &ColumnarValue::Array(value),
1073            &ColumnarValue::Scalar(ScalarValue::Int32(Some(2))),
1074            4,
1075            &DataType::Float64,
1076        )
1077        .expect("failed to initialize function round");
1078        let ColumnarValue::Array(result) = result else {
1079            panic!("expected an array result");
1080        };
1081        let floats =
1082            as_float64_array(&result).expect("failed to initialize function round");
1083
1084        let expected = Float64Array::from(vec![Some(125.23), None, Some(-1.56), None]);
1085
1086        assert_eq!(floats, &expected);
1087    }
1088
1089    #[test]
1090    fn test_round_f32_one_input() {
1091        let args: Vec<ArrayRef> = vec![
1092            Arc::new(Float32Array::from(vec![125.2345, 12.345, 1.234, 0.1234])), // input
1093        ];
1094
1095        let result = round_arrays(Arc::clone(&args[0]), None)
1096            .expect("failed to initialize function round");
1097        let floats =
1098            as_float32_array(&result).expect("failed to initialize function round");
1099
1100        let expected = Float32Array::from(vec![125.0, 12.0, 1.0, 0.0]);
1101
1102        assert_eq!(floats, &expected);
1103    }
1104
1105    #[test]
1106    fn test_round_f64_one_input() {
1107        let args: Vec<ArrayRef> = vec![
1108            Arc::new(Float64Array::from(vec![125.2345, 12.345, 1.234, 0.1234])), // input
1109        ];
1110
1111        let result = round_arrays(Arc::clone(&args[0]), None)
1112            .expect("failed to initialize function round");
1113        let floats =
1114            as_float64_array(&result).expect("failed to initialize function round");
1115
1116        let expected = Float64Array::from(vec![125.0, 12.0, 1.0, 0.0]);
1117
1118        assert_eq!(floats, &expected);
1119    }
1120
1121    #[test]
1122    fn test_round_f32_cast_fail() {
1123        let args: Vec<ArrayRef> = vec![
1124            Arc::new(Float64Array::from(vec![125.2345])), // input
1125            Arc::new(Int64Array::from(vec![2147483648])), // decimal_places
1126        ];
1127
1128        let result = round_arrays(Arc::clone(&args[0]), Some(Arc::clone(&args[1])));
1129
1130        assert!(result.is_err());
1131        assert!(matches!(
1132            result,
1133            Err(DataFusionError::ArrowError(_, _)) | Err(DataFusionError::Execution(_))
1134        ));
1135    }
1136}