Skip to main content

datafusion_spark/function/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 std::sync::Arc;
19
20use arrow::array::*;
21use arrow::datatypes::{
22    ArrowNativeTypeOp, DataType, Decimal32Type, Decimal64Type, Decimal128Type,
23    Decimal256Type, Float16Type, Float32Type, Float64Type, Int8Type, Int16Type,
24    Int32Type, Int64Type, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
25};
26use datafusion_common::types::{
27    NativeType, logical_float32, logical_float64, logical_int32,
28};
29use datafusion_common::{Result, ScalarValue, exec_err, not_impl_err};
30use datafusion_expr::{
31    Coercion, ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature,
32    TypeSignatureClass, Volatility,
33};
34
35/// Spark-compatible `round` expression
36/// <https://spark.apache.org/docs/latest/api/sql/index.html#round>
37///
38/// Rounds the value of `expr` to `scale` decimal places using HALF_UP rounding mode.
39/// Returns the same type as the input expression.
40///
41/// - `round(expr)` rounds to 0 decimal places (default scale = 0)
42/// - `round(expr, scale)` rounds to `scale` decimal places
43/// - For integer types with negative scale: `round(25, -1)` → `30`
44/// - Uses HALF_UP rounding: 2.5 → 3, -2.5 → -3 (away from zero)
45///
46/// Supported types: Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64,
47/// Float16, Float32, Float64, Decimal32, Decimal64, Decimal128, Decimal256
48#[derive(Debug, PartialEq, Eq, Hash)]
49pub struct SparkRound {
50    signature: Signature,
51}
52
53impl Default for SparkRound {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl SparkRound {
60    pub fn new() -> Self {
61        let decimal = Coercion::new_exact(TypeSignatureClass::Decimal);
62        let integer = Coercion::new_exact(TypeSignatureClass::Integer);
63        let decimal_places = Coercion::new_implicit(
64            TypeSignatureClass::Native(logical_int32()),
65            vec![TypeSignatureClass::Integer],
66            NativeType::Int32,
67        );
68        let float32 = Coercion::new_exact(TypeSignatureClass::Native(logical_float32()));
69        let float64 = Coercion::new_implicit(
70            TypeSignatureClass::Native(logical_float64()),
71            vec![TypeSignatureClass::Numeric],
72            NativeType::Float64,
73        );
74        Self {
75            signature: Signature::one_of(
76                vec![
77                    // round(decimal, scale)
78                    TypeSignature::Coercible(vec![
79                        decimal.clone(),
80                        decimal_places.clone(),
81                    ]),
82                    // round(decimal)
83                    TypeSignature::Coercible(vec![decimal]),
84                    // round(integer, scale)
85                    TypeSignature::Coercible(vec![
86                        integer.clone(),
87                        decimal_places.clone(),
88                    ]),
89                    // round(integer)
90                    TypeSignature::Coercible(vec![integer]),
91                    // round(float32, scale)
92                    TypeSignature::Coercible(vec![
93                        float32.clone(),
94                        decimal_places.clone(),
95                    ]),
96                    // round(float32)
97                    TypeSignature::Coercible(vec![float32]),
98                    // round(float64, scale)
99                    TypeSignature::Coercible(vec![float64.clone(), decimal_places]),
100                    // round(float64)
101                    TypeSignature::Coercible(vec![float64]),
102                ],
103                Volatility::Immutable,
104            ),
105        }
106    }
107}
108
109impl ScalarUDFImpl for SparkRound {
110    fn name(&self) -> &str {
111        "round"
112    }
113
114    fn signature(&self) -> &Signature {
115        &self.signature
116    }
117
118    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
119        Ok(arg_types[0].clone())
120    }
121
122    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
123        spark_round(&args.args, args.config_options.execution.enable_ansi_mode)
124    }
125}
126
127/// Extract the scale (decimal places) from the second argument.
128/// Returns `Some(0)` if no second argument is provided.
129/// Returns `None` if the scale argument is NULL (Spark returns NULL for `round(expr, NULL)`).
130fn get_scale(args: &[ColumnarValue]) -> Result<Option<i32>> {
131    if args.len() < 2 {
132        return Ok(Some(0));
133    }
134
135    match &args[1] {
136        ColumnarValue::Scalar(ScalarValue::Int8(Some(v))) => Ok(Some(i32::from(*v))),
137        ColumnarValue::Scalar(ScalarValue::Int16(Some(v))) => Ok(Some(i32::from(*v))),
138        ColumnarValue::Scalar(ScalarValue::Int32(Some(v))) => Ok(Some(*v)),
139        ColumnarValue::Scalar(ScalarValue::Int64(Some(v))) => {
140            i32::try_from(*v).map(Some).map_err(|_| {
141                (exec_err!("round scale {v} is out of supported i32 range")
142                    as Result<(), _>)
143                    .unwrap_err()
144            })
145        }
146        ColumnarValue::Scalar(ScalarValue::UInt8(Some(v))) => Ok(Some(i32::from(*v))),
147        ColumnarValue::Scalar(ScalarValue::UInt16(Some(v))) => Ok(Some(i32::from(*v))),
148        ColumnarValue::Scalar(ScalarValue::UInt32(Some(v))) => {
149            i32::try_from(*v).map(Some).map_err(|_| {
150                (exec_err!("round scale {v} is out of supported i32 range")
151                    as Result<(), _>)
152                    .unwrap_err()
153            })
154        }
155        ColumnarValue::Scalar(ScalarValue::UInt64(Some(v))) => {
156            i32::try_from(*v).map(Some).map_err(|_| {
157                (exec_err!("round scale {v} is out of supported i32 range")
158                    as Result<(), _>)
159                    .unwrap_err()
160            })
161        }
162        ColumnarValue::Scalar(sv) if sv.is_null() => Ok(None),
163        other => exec_err!("Unsupported type for round scale: {}", other.data_type()),
164    }
165}
166
167/// Round a floating-point value to the given number of decimal places using
168/// HALF_UP rounding mode (ties round away from zero).
169///
170/// This matches Spark's `RoundBase` behaviour for `FloatType` / `DoubleType`,
171/// which internally converts the value to `BigDecimal` and rounds with
172/// `RoundingMode.HALF_UP`.
173///
174/// # Arguments
175/// * `value` – the floating-point number to round
176/// * `scale` – number of decimal places to keep.
177///   - `scale >= 0`: rounds to that many fractional digits
178///     (e.g. `round_float(2.345, 2) == 2.35`)
179///   - `scale < 0`:  rounds to the left of the decimal point
180///     (e.g. `round_float(125.0, -1) == 130.0`)
181///
182/// # Examples
183/// ```text
184/// round_float(2.5,  0) →  3.0   // half rounds up
185/// round_float(-2.5, 0) → -3.0   // half rounds away from zero
186/// round_float(1.4,  0) →  1.0
187/// round_float(125.0, -1) → 130.0
188/// ```
189fn round_float<T: num_traits::Float>(value: T, scale: i32) -> T {
190    if scale >= 0 {
191        let factor = T::from(10.0f64.powi(scale)).unwrap_or_else(T::infinity);
192        if factor.is_infinite() {
193            // Very large positive scale — value is already precise enough, return as-is
194            return value;
195        }
196        (value * factor).round() / factor
197    } else {
198        let factor = T::from(10.0f64.powi(-scale)).unwrap_or_else(T::infinity);
199        if factor.is_infinite() {
200            // Very large negative scale — any finite value rounds to 0
201            return T::zero();
202        }
203        (value / factor).round() * factor
204    }
205}
206
207/// Round an integer value to the given scale using HALF_UP rounding mode.
208///
209/// Only meaningful when `scale` is negative — a non-negative scale leaves
210/// the integer unchanged because integers have no fractional part.
211///
212/// This matches Spark's `RoundBase` behaviour for `ByteType`, `ShortType`,
213/// `IntegerType`, and `LongType`, which round to the nearest power-of-ten
214/// boundary and return the same integer type.
215///
216/// In ANSI mode, overflow conditions return an error instead of wrapping.
217///
218/// # Arguments
219/// * `value` – the integer to round (widened to `i64` by callers)
220/// * `scale` – rounding position relative to the ones digit.
221///   - `scale >= 0`:  returns `value` as-is
222///   - `scale == -1`: rounds to the nearest 10
223///   - `scale == -2`: rounds to the nearest 100
224///   - If `10^|scale|` overflows `i64`, returns `0`
225/// * `enable_ansi_mode` – when true, overflow returns an error
226///
227/// # Examples
228/// ```text
229/// round_integer(25,   -1, false) →  Ok(30)
230/// round_integer(-25,  -1, false) → Ok(-30)
231/// round_integer(123,  -1, false) →  Ok(120)
232/// round_integer(150,  -2, false) →  Ok(200)
233/// round_integer(42,    2, false) →   Ok(42)   // no-op for positive scale
234/// round_integer(42,  -10, false) →    Ok(0)   // factor overflows → 0
235/// ```
236fn round_integer(value: i64, scale: i32, enable_ansi_mode: bool) -> Result<i64> {
237    if scale >= 0 {
238        return Ok(value);
239    }
240    let abs_scale = (-scale) as u32;
241    let Some(factor) = 10_i64.checked_pow(abs_scale) else {
242        return Ok(0);
243    };
244    let remainder = value % factor;
245    let threshold = factor / 2;
246    let result = if remainder >= threshold {
247        if enable_ansi_mode {
248            value
249                .checked_sub(remainder)
250                .and_then(|v| v.checked_add(factor))
251                .ok_or_else(|| {
252                    (exec_err!("Int64 overflow on round({value}, {scale})")
253                        as Result<(), _>)
254                        .unwrap_err()
255                })?
256        } else {
257            value.wrapping_sub(remainder).wrapping_add(factor)
258        }
259    } else if remainder <= -threshold {
260        if enable_ansi_mode {
261            value
262                .checked_sub(remainder)
263                .and_then(|v| v.checked_sub(factor))
264                .ok_or_else(|| {
265                    (exec_err!("Int64 overflow on round({value}, {scale})")
266                        as Result<(), _>)
267                        .unwrap_err()
268                })?
269        } else {
270            value.wrapping_sub(remainder).wrapping_sub(factor)
271        }
272    } else {
273        value - remainder
274    };
275    Ok(result)
276}
277
278// ---------------------------------------------------------------------------
279// Decimal rounding using ArrowNativeTypeOp (HALF_UP)
280// ---------------------------------------------------------------------------
281
282/// Round a decimal value represented as its unscaled integer using HALF_UP
283/// rounding mode (ties round away from zero).
284///
285/// This matches Spark's `RoundBase` behaviour for `DecimalType`, which calls
286/// `BigDecimal.setScale(scale, RoundingMode.HALF_UP)`.
287///
288/// Decimals are stored as `(unscaled_value, precision, scale)` where the real
289/// value equals `unscaled_value * 10^(-scale)`.  This function operates on the
290/// unscaled integer directly:
291///
292/// 1. Compute `diff = input_scale - decimal_places`.
293///    If `diff <= 0` the requested precision is finer than (or equal to) the
294///    stored scale, so nothing needs to be rounded — return as-is.
295/// 2. Divide by `10^diff` to shift the rounding boundary into the ones digit.
296/// 3. Inspect the remainder to decide whether to round up or down (HALF_UP).
297/// 4. Multiply back by `10^diff` so the result is expressed at the original
298///    `input_scale`.
299///
300/// # Arguments
301/// * `value`          – unscaled decimal value
302/// * `input_scale`    – scale of the incoming decimal
303/// * `decimal_places` – number of fractional digits to keep (may be negative)
304///
305/// # Returns
306/// The rounded unscaled value at the same `input_scale`, or an error
307/// on overflow.
308///
309/// # Examples
310/// ```text
311/// // 2.5 (unscaled 25, scale 1) rounded to 0 places → 3.0 (unscaled 30)
312/// round_decimal(25_i128, 1, 0)  → Ok(30)
313///
314/// // 2.345 (unscaled 2345, scale 3) rounded to 2 places → 2.350 (unscaled 2350)
315/// round_decimal(2345_i128, 3, 2) → Ok(2350)
316/// ```
317fn round_decimal<V: ArrowNativeTypeOp>(
318    value: V,
319    input_scale: i8,
320    decimal_places: i32,
321) -> Result<V> {
322    let diff = i64::from(input_scale) - i64::from(decimal_places);
323    if diff <= 0 {
324        // Nothing to round – the requested precision is finer than (or equal to) the
325        // stored scale.
326        return Ok(value);
327    }
328
329    let diff = diff as u32;
330
331    let one = V::ONE;
332    let two = V::from_usize(2).ok_or_else(|| {
333        (exec_err!("Internal error: could not create constant 2") as Result<(), _>)
334            .unwrap_err()
335    })?;
336    let ten = V::from_usize(10).ok_or_else(|| {
337        (exec_err!("Internal error: could not create constant 10") as Result<(), _>)
338            .unwrap_err()
339    })?;
340
341    let Ok(factor) = ten.pow_checked(diff) else {
342        // 10^diff overflows the decimal type — the rounding position is beyond
343        // the representable range, so any value rounds to 0.
344        // This matches Spark's BigDecimal.setScale behavior where rounding to a
345        // scale far beyond the number's magnitude yields 0.
346        return Ok(V::ZERO);
347    };
348
349    let mut quotient = value.div_wrapping(factor);
350    let remainder = value.mod_wrapping(factor);
351
352    // HALF_UP: round away from zero when remainder is exactly half
353    let threshold = factor.div_wrapping(two);
354    if remainder >= threshold {
355        quotient = quotient.add_checked(one).map_err(|_| {
356            (exec_err!("Overflow while rounding decimal") as Result<(), _>).unwrap_err()
357        })?;
358    } else if remainder <= threshold.neg_wrapping() {
359        quotient = quotient.sub_checked(one).map_err(|_| {
360            (exec_err!("Overflow while rounding decimal") as Result<(), _>).unwrap_err()
361        })?;
362    }
363
364    // Re-scale the quotient back to `input_scale` so the returned unscaled integer is
365    // at the original scale. `factor` is already `10^diff` which is exactly the shift
366    // we need.
367    quotient.mul_checked(factor).map_err(|_| {
368        (exec_err!("Overflow while rounding decimal") as Result<(), _>).unwrap_err()
369    })
370}
371
372// ---------------------------------------------------------------------------
373// Macros for array dispatch
374// ---------------------------------------------------------------------------
375
376macro_rules! impl_integer_array_round {
377    ($array:expr, $arrow_type:ty, $scale:expr, $enable_ansi_mode:expr) => {{
378        let array = $array.as_primitive::<$arrow_type>();
379        type Native = <$arrow_type as arrow::datatypes::ArrowPrimitiveType>::Native;
380        let result: PrimitiveArray<$arrow_type> = if $enable_ansi_mode {
381            array.try_unary(|x| {
382                let v = round_integer(x as i64, $scale, true)?;
383                Native::try_from(v).map_err(|_| {
384                    (exec_err!(
385                        "{} overflow on round({x}, {})",
386                        stringify!($arrow_type),
387                        $scale
388                    ) as Result<(), _>)
389                        .unwrap_err()
390                })
391            })?
392        } else {
393            array.unary(|x| round_integer(x as i64, $scale, false).unwrap() as Native)
394        };
395        Ok(ColumnarValue::Array(Arc::new(result)))
396    }};
397}
398
399macro_rules! impl_float_array_round {
400    ($array:expr, $arrow_type:ty, $scale:expr) => {{
401        let array = $array.as_primitive::<$arrow_type>();
402        let result: PrimitiveArray<$arrow_type> = array.unary(|x| round_float(x, $scale));
403        Ok(ColumnarValue::Array(Arc::new(result)))
404    }};
405}
406
407macro_rules! impl_decimal_array_round {
408    ($array:expr, $arrow_type:ty, $input_scale:expr, $scale:expr) => {{
409        let array = $array.as_primitive::<$arrow_type>();
410        let result: PrimitiveArray<$arrow_type> = array
411            .try_unary(|x| round_decimal(x, $input_scale, $scale))?
412            .with_data_type($array.data_type().clone());
413        Ok(ColumnarValue::Array(Arc::new(result)))
414    }};
415}
416
417// ---------------------------------------------------------------------------
418// Core dispatch
419// ---------------------------------------------------------------------------
420
421fn spark_round(args: &[ColumnarValue], enable_ansi_mode: bool) -> Result<ColumnarValue> {
422    if args.is_empty() || args.len() > 2 {
423        return exec_err!("round requires 1 or 2 arguments, got {}", args.len());
424    }
425
426    let scale = match get_scale(args)? {
427        Some(s) => s,
428        None => {
429            // NULL scale → return NULL with the same data type as the first argument
430            return Ok(ColumnarValue::Scalar(ScalarValue::try_from(
431                args[0].data_type(),
432            )?));
433        }
434    };
435
436    match &args[0] {
437        ColumnarValue::Array(array) => match array.data_type() {
438            DataType::Null => Ok(args[0].clone()),
439
440            // Integer types
441            DataType::Int8 => {
442                impl_integer_array_round!(array, Int8Type, scale, enable_ansi_mode)
443            }
444            DataType::Int16 => {
445                impl_integer_array_round!(array, Int16Type, scale, enable_ansi_mode)
446            }
447            DataType::Int32 => {
448                impl_integer_array_round!(array, Int32Type, scale, enable_ansi_mode)
449            }
450            DataType::Int64 => {
451                impl_integer_array_round!(array, Int64Type, scale, enable_ansi_mode)
452            }
453
454            // Unsigned integer types
455            DataType::UInt8 => {
456                impl_integer_array_round!(array, UInt8Type, scale, enable_ansi_mode)
457            }
458            DataType::UInt16 => {
459                impl_integer_array_round!(array, UInt16Type, scale, enable_ansi_mode)
460            }
461            DataType::UInt32 => {
462                impl_integer_array_round!(array, UInt32Type, scale, enable_ansi_mode)
463            }
464            DataType::UInt64 => {
465                let array = array.as_primitive::<UInt64Type>();
466                let result: PrimitiveArray<UInt64Type> = array.try_unary(|x| {
467                    let v_i64 = i64::try_from(x).map_err(|_| {
468                        (exec_err!(
469                            "round: UInt64 value {x} exceeds i64::MAX and cannot be rounded"
470                        ) as Result<(), _>)
471                            .unwrap_err()
472                    })?;
473                    round_integer(v_i64, scale, enable_ansi_mode)
474                        .map(|v| v as u64)
475                })?;
476                Ok(ColumnarValue::Array(Arc::new(result)))
477            }
478
479            // Float types
480            DataType::Float16 => impl_float_array_round!(array, Float16Type, scale),
481            DataType::Float32 => impl_float_array_round!(array, Float32Type, scale),
482            DataType::Float64 => impl_float_array_round!(array, Float64Type, scale),
483
484            // Decimal types
485            DataType::Decimal32(_, input_scale) => {
486                impl_decimal_array_round!(array, Decimal32Type, *input_scale, scale)
487            }
488            DataType::Decimal64(_, input_scale) => {
489                impl_decimal_array_round!(array, Decimal64Type, *input_scale, scale)
490            }
491            DataType::Decimal128(_, input_scale) => {
492                impl_decimal_array_round!(array, Decimal128Type, *input_scale, scale)
493            }
494            DataType::Decimal256(_, input_scale) => {
495                impl_decimal_array_round!(array, Decimal256Type, *input_scale, scale)
496            }
497
498            dt => not_impl_err!("Unsupported data type for Spark round(): {dt}"),
499        },
500
501        ColumnarValue::Scalar(sv) => match sv {
502            ScalarValue::Null => Ok(args[0].clone()),
503            _ if sv.is_null() => Ok(args[0].clone()),
504
505            // Integer scalars
506            ScalarValue::Int8(Some(v)) => {
507                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
508                let result = if enable_ansi_mode {
509                    i8::try_from(r).map_err(|_| {
510                        (exec_err!("Int8 overflow on round({v}, {scale})")
511                            as Result<(), _>)
512                            .unwrap_err()
513                    })?
514                } else {
515                    r as i8
516                };
517                Ok(ColumnarValue::Scalar(ScalarValue::Int8(Some(result))))
518            }
519            ScalarValue::Int16(Some(v)) => {
520                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
521                let result = if enable_ansi_mode {
522                    i16::try_from(r).map_err(|_| {
523                        (exec_err!("Int16 overflow on round({v}, {scale})")
524                            as Result<(), _>)
525                            .unwrap_err()
526                    })?
527                } else {
528                    r as i16
529                };
530                Ok(ColumnarValue::Scalar(ScalarValue::Int16(Some(result))))
531            }
532            ScalarValue::Int32(Some(v)) => {
533                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
534                let result = if enable_ansi_mode {
535                    i32::try_from(r).map_err(|_| {
536                        (exec_err!("Int32 overflow on round({v}, {scale})")
537                            as Result<(), _>)
538                            .unwrap_err()
539                    })?
540                } else {
541                    r as i32
542                };
543                Ok(ColumnarValue::Scalar(ScalarValue::Int32(Some(result))))
544            }
545            ScalarValue::Int64(Some(v)) => {
546                let result = round_integer(*v, scale, enable_ansi_mode)?;
547                Ok(ColumnarValue::Scalar(ScalarValue::Int64(Some(result))))
548            }
549
550            // Unsigned integer scalars
551            ScalarValue::UInt8(Some(v)) => {
552                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
553                let result = if enable_ansi_mode {
554                    u8::try_from(r).map_err(|_| {
555                        (exec_err!("UInt8 overflow on round({v}, {scale})")
556                            as Result<(), _>)
557                            .unwrap_err()
558                    })?
559                } else {
560                    r as u8
561                };
562                Ok(ColumnarValue::Scalar(ScalarValue::UInt8(Some(result))))
563            }
564            ScalarValue::UInt16(Some(v)) => {
565                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
566                let result = if enable_ansi_mode {
567                    u16::try_from(r).map_err(|_| {
568                        (exec_err!("UInt16 overflow on round({v}, {scale})")
569                            as Result<(), _>)
570                            .unwrap_err()
571                    })?
572                } else {
573                    r as u16
574                };
575                Ok(ColumnarValue::Scalar(ScalarValue::UInt16(Some(result))))
576            }
577            ScalarValue::UInt32(Some(v)) => {
578                let r = round_integer(i64::from(*v), scale, enable_ansi_mode)?;
579                let result = if enable_ansi_mode {
580                    u32::try_from(r).map_err(|_| {
581                        (exec_err!("UInt32 overflow on round({v}, {scale})")
582                            as Result<(), _>)
583                            .unwrap_err()
584                    })?
585                } else {
586                    r as u32
587                };
588                Ok(ColumnarValue::Scalar(ScalarValue::UInt32(Some(result))))
589            }
590            ScalarValue::UInt64(Some(v)) => {
591                let v_i64 = i64::try_from(*v).map_err(|_| {
592                    (exec_err!(
593                        "round: UInt64 value {v} exceeds i64::MAX and cannot be rounded"
594                    ) as Result<(), _>)
595                        .unwrap_err()
596                })?;
597                let result = round_integer(v_i64, scale, enable_ansi_mode)?;
598                Ok(ColumnarValue::Scalar(ScalarValue::UInt64(Some(
599                    result as u64,
600                ))))
601            }
602
603            // Float scalars
604            ScalarValue::Float16(Some(v)) => {
605                let result = round_float(*v, scale);
606                Ok(ColumnarValue::Scalar(ScalarValue::Float16(Some(result))))
607            }
608            ScalarValue::Float32(Some(v)) => {
609                let result = round_float(*v, scale);
610                Ok(ColumnarValue::Scalar(ScalarValue::Float32(Some(result))))
611            }
612            ScalarValue::Float64(Some(v)) => {
613                let result = round_float(*v, scale);
614                Ok(ColumnarValue::Scalar(ScalarValue::Float64(Some(result))))
615            }
616
617            // Decimal scalars
618            ScalarValue::Decimal32(Some(v), precision, input_scale) => {
619                let rounded = round_decimal(*v, *input_scale, scale)?;
620                Ok(ColumnarValue::Scalar(ScalarValue::Decimal32(
621                    Some(rounded),
622                    *precision,
623                    *input_scale,
624                )))
625            }
626            ScalarValue::Decimal64(Some(v), precision, input_scale) => {
627                let rounded = round_decimal(*v, *input_scale, scale)?;
628                Ok(ColumnarValue::Scalar(ScalarValue::Decimal64(
629                    Some(rounded),
630                    *precision,
631                    *input_scale,
632                )))
633            }
634            ScalarValue::Decimal128(Some(v), precision, input_scale) => {
635                let rounded = round_decimal(*v, *input_scale, scale)?;
636                Ok(ColumnarValue::Scalar(ScalarValue::Decimal128(
637                    Some(rounded),
638                    *precision,
639                    *input_scale,
640                )))
641            }
642            ScalarValue::Decimal256(Some(v), precision, input_scale) => {
643                let rounded = round_decimal(*v, *input_scale, scale)?;
644                Ok(ColumnarValue::Scalar(ScalarValue::Decimal256(
645                    Some(rounded),
646                    *precision,
647                    *input_scale,
648                )))
649            }
650
651            dt => not_impl_err!("Unsupported data type for Spark round(): {dt}"),
652        },
653    }
654}