Skip to main content

arrow_cast/cast/
decimal.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5use crate::cast::*;
6use crate::parse::{DecimalParseError, parse_decimal_checked};
7
8/// A utility trait that provides checked conversions between
9/// decimal types inspired by [`NumCast`]
10pub trait DecimalCast: Sized {
11    /// Convert the decimal to an i32
12    fn to_i32(self) -> Option<i32>;
13
14    /// Convert the decimal to an i64
15    fn to_i64(self) -> Option<i64>;
16
17    /// Convert the decimal to an i128
18    fn to_i128(self) -> Option<i128>;
19
20    /// Convert the decimal to an i256
21    fn to_i256(self) -> Option<i256>;
22
23    /// Convert a decimal from a decimal
24    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self>;
25
26    /// Convert a decimal from a f64
27    fn from_f64(n: f64) -> Option<Self>;
28}
29
30impl DecimalCast for i32 {
31    fn to_i32(self) -> Option<i32> {
32        Some(self)
33    }
34
35    fn to_i64(self) -> Option<i64> {
36        Some(self as i64)
37    }
38
39    fn to_i128(self) -> Option<i128> {
40        Some(self as i128)
41    }
42
43    fn to_i256(self) -> Option<i256> {
44        Some(i256::from_i128(self as i128))
45    }
46
47    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
48        n.to_i32()
49    }
50
51    fn from_f64(n: f64) -> Option<Self> {
52        n.to_i32()
53    }
54}
55
56impl DecimalCast for i64 {
57    fn to_i32(self) -> Option<i32> {
58        i32::try_from(self).ok()
59    }
60
61    fn to_i64(self) -> Option<i64> {
62        Some(self)
63    }
64
65    fn to_i128(self) -> Option<i128> {
66        Some(self as i128)
67    }
68
69    fn to_i256(self) -> Option<i256> {
70        Some(i256::from_i128(self as i128))
71    }
72
73    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
74        n.to_i64()
75    }
76
77    fn from_f64(n: f64) -> Option<Self> {
78        // Call implementation explicitly otherwise this resolves to `to_i64`
79        // in arrow-buffer that behaves differently.
80        num_traits::ToPrimitive::to_i64(&n)
81    }
82}
83
84impl DecimalCast for i128 {
85    fn to_i32(self) -> Option<i32> {
86        i32::try_from(self).ok()
87    }
88
89    fn to_i64(self) -> Option<i64> {
90        i64::try_from(self).ok()
91    }
92
93    fn to_i128(self) -> Option<i128> {
94        Some(self)
95    }
96
97    fn to_i256(self) -> Option<i256> {
98        Some(i256::from_i128(self))
99    }
100
101    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
102        n.to_i128()
103    }
104
105    fn from_f64(n: f64) -> Option<Self> {
106        n.to_i128()
107    }
108}
109
110impl DecimalCast for i256 {
111    fn to_i32(self) -> Option<i32> {
112        self.to_i128().map(|x| i32::try_from(x).ok())?
113    }
114
115    fn to_i64(self) -> Option<i64> {
116        self.to_i128().map(|x| i64::try_from(x).ok())?
117    }
118
119    fn to_i128(self) -> Option<i128> {
120        self.to_i128()
121    }
122
123    fn to_i256(self) -> Option<i256> {
124        Some(self)
125    }
126
127    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
128        n.to_i256()
129    }
130
131    fn from_f64(n: f64) -> Option<Self> {
132        i256::from_f64(n)
133    }
134}
135
136/// Construct closures to upscale decimals from `(input_precision, input_scale)` to
137/// `(output_precision, output_scale)`.
138///
139/// Returns `(f_fallible, f_infallible)` where:
140/// * `f_fallible` yields `None` when the requested cast would overflow
141/// * `f_infallible` is present only when every input is guaranteed to succeed; otherwise it is `None`
142///   and callers must fall back to `f_fallible`
143///
144/// Returns `None` if the required scale increase `delta_scale = output_scale - input_scale`
145/// exceeds the supported precomputed precision table `O::MAX_FOR_EACH_PRECISION`.
146/// In that case, the caller should treat this as an overflow for the output scale
147/// and handle it accordingly (e.g., return a cast error).
148#[expect(clippy::type_complexity)]
149fn make_upscaler<I: DecimalType, O: DecimalType>(
150    input_precision: u8,
151    input_scale: i8,
152    output_precision: u8,
153    output_scale: i8,
154) -> Option<(
155    impl Fn(I::Native) -> Option<O::Native>,
156    Option<impl Fn(I::Native) -> O::Native>,
157)>
158where
159    I::Native: DecimalCast + ArrowNativeTypeOp,
160    O::Native: DecimalCast + ArrowNativeTypeOp,
161{
162    let delta_scale = output_scale as i16 - input_scale as i16;
163
164    // O::MAX_FOR_EACH_PRECISION[k] stores 10^k - 1 (e.g., 9, 99, 999, ...).
165    // Adding 1 yields exactly 10^k without computing a power at runtime.
166    // Using the precomputed table avoids pow(10, k) and its checked/overflow
167    // handling, which is faster and simpler for scaling by 10^delta_scale.
168    let max = O::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
169    let mul = max.add_wrapping(O::Native::ONE);
170    let f_fallible = move |x| O::Native::from_decimal(x)?.mul_checked(mul).ok();
171
172    // if the gain in precision (digits) is greater than the multiplication due to scaling
173    // every number will fit into the output type
174    // Example: If we are starting with any number of precision 5 [xxxxx],
175    // then an increase of scale by 3 will have the following effect on the representation:
176    // [xxxxx] -> [xxxxx000], so for the cast to be infallible, the output type
177    // needs to provide at least 8 digits precision
178    let is_infallible_cast = (input_precision as i16) + delta_scale <= (output_precision as i16);
179    let f_infallible = is_infallible_cast
180        .then_some(move |x| O::Native::from_decimal(x).unwrap().mul_wrapping(mul));
181    Some((f_fallible, f_infallible))
182}
183
184/// Construct closures to downscale decimals from `(input_precision, input_scale)` to
185/// `(output_precision, output_scale)`.
186///
187/// Returns `(f_fallible, f_infallible)` where:
188/// * `f_fallible` yields `None` when the requested cast would overflow
189/// * `f_infallible` is present only when every input is guaranteed to succeed; otherwise it is `None`
190///   and callers must fall back to `f_fallible`
191///
192/// Returns `None` if the required scale reduction `delta_scale = input_scale - output_scale`
193/// exceeds the supported precomputed precision table `I::MAX_FOR_EACH_PRECISION`.
194/// In this scenario, any value would round to zero (e.g., dividing by 10^k where k exceeds the
195/// available precision). Callers should therefore produce zero values (preserving nulls) rather
196/// than returning an error.
197#[expect(clippy::type_complexity)]
198fn make_downscaler<I: DecimalType, O: DecimalType>(
199    input_precision: u8,
200    input_scale: i8,
201    output_precision: u8,
202    output_scale: i8,
203) -> Option<(
204    impl Fn(I::Native) -> Option<O::Native>,
205    Option<impl Fn(I::Native) -> O::Native>,
206)>
207where
208    I::Native: DecimalCast + ArrowNativeTypeOp,
209    O::Native: DecimalCast + ArrowNativeTypeOp,
210{
211    let delta_scale = input_scale as i16 - output_scale as i16;
212
213    // delta_scale is guaranteed to be > 0, but may also be larger than I::MAX_PRECISION. If so, the
214    // scale change divides out more digits than the input has precision and the result of the cast
215    // is always zero. For example, if we try to apply delta_scale=10 a decimal32 value, the largest
216    // possible result is 999999999/10000000000 = 0.0999999999, which rounds to zero. Smaller values
217    // (e.g. 1/10000000000) or larger delta_scale (e.g. 999999999/10000000000000) produce even
218    // smaller results, which also round to zero. In that case, just return an array of zeros.
219    let max = I::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
220
221    let div = max.add_wrapping(I::Native::ONE);
222    let half = div.div_wrapping(I::Native::ONE.add_wrapping(I::Native::ONE));
223    let half_neg = half.neg_wrapping();
224
225    let f_fallible = move |x: I::Native| {
226        // div is >= 10 and so this cannot overflow
227        let (d, r) = x.div_rem_wrapping(div);
228
229        // Round result
230        let adjusted = match x >= I::Native::ZERO {
231            true if r >= half => d.add_wrapping(I::Native::ONE),
232            false if r <= half_neg => d.sub_wrapping(I::Native::ONE),
233            _ => d,
234        };
235        O::Native::from_decimal(adjusted)
236    };
237
238    // if the reduction of the input number through scaling (dividing) is greater
239    // than a possible precision loss (plus potential increase via rounding)
240    // every input number will fit into the output type
241    // Example: If we are starting with any number of precision 5 [xxxxx],
242    // then and decrease the scale by 3 will have the following effect on the representation:
243    // [xxxxx] -> [xx] (+ 1 possibly, due to rounding).
244    // The rounding may add a digit, so the cast to be infallible,
245    // the output type needs to have at least 3 digits of precision.
246    // e.g. Decimal(5, 3) 99.999 to Decimal(3, 0) will result in 100:
247    // [99999] -> [99] + 1 = [100], a cast to Decimal(2, 0) would not be possible
248    let is_infallible_cast = (input_precision as i16) - delta_scale < (output_precision as i16);
249    let f_infallible = is_infallible_cast.then_some(move |x| f_fallible(x).unwrap());
250    Some((f_fallible, f_infallible))
251}
252
253/// Apply the rescaler function to the value.
254/// If the rescaler is infallible, use the infallible function.
255/// Otherwise, use the fallible function and validate the precision.
256fn apply_rescaler<I: DecimalType, O: DecimalType>(
257    value: I::Native,
258    output_precision: u8,
259    f: impl Fn(I::Native) -> Option<O::Native>,
260    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
261) -> Option<O::Native>
262where
263    I::Native: DecimalCast,
264    O::Native: DecimalCast,
265{
266    if let Some(f_infallible) = f_infallible {
267        Some(f_infallible(value))
268    } else {
269        f(value).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
270    }
271}
272
273/// Rescales a decimal value from `(input_precision, input_scale)` to
274/// `(output_precision, output_scale)` and returns the converted number when it fits
275/// within the output precision.
276///
277/// The function first validates that the requested precision and scale are supported for
278/// both the source and destination decimal types. It then either upscales (multiplying
279/// by an appropriate power of ten) or downscales (dividing with rounding) the input value.
280/// When the scaling factor exceeds the precision table of the destination type, the value
281/// is treated as an overflow for upscaling, or rounded to zero for downscaling (as any
282/// possible result would be zero at the requested scale).
283///
284/// This mirrors the column-oriented helpers of decimal casting but operates on a single value
285/// (row-level) instead of an entire array.
286///
287/// Returns `None` if the value cannot be represented with the requested precision.
288pub fn rescale_decimal<I: DecimalType, O: DecimalType>(
289    value: I::Native,
290    input_precision: u8,
291    input_scale: i8,
292    output_precision: u8,
293    output_scale: i8,
294) -> Option<O::Native>
295where
296    I::Native: DecimalCast + ArrowNativeTypeOp,
297    O::Native: DecimalCast + ArrowNativeTypeOp,
298{
299    validate_decimal_precision_and_scale::<I>(input_precision, input_scale).ok()?;
300    validate_decimal_precision_and_scale::<O>(output_precision, output_scale).ok()?;
301
302    if input_scale <= output_scale {
303        let (f, f_infallible) =
304            make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)?;
305        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
306    } else {
307        let Some((f, f_infallible)) =
308            make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
309        else {
310            // Scale reduction exceeds supported precision; result mathematically rounds to zero
311            return Some(O::Native::ZERO);
312        };
313        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
314    }
315}
316
317fn cast_decimal_to_decimal_error<I, O>(
318    output_precision: u8,
319    output_scale: i8,
320) -> impl Fn(<I as ArrowPrimitiveType>::Native) -> ArrowError
321where
322    I: DecimalType,
323    O: DecimalType,
324    I::Native: DecimalCast + ArrowNativeTypeOp,
325    O::Native: DecimalCast + ArrowNativeTypeOp,
326{
327    move |x: I::Native| {
328        ArrowError::CastError(format!(
329            "Cannot cast to {}({}, {}). Overflowing on {:?}",
330            O::PREFIX,
331            output_precision,
332            output_scale,
333            x
334        ))
335    }
336}
337
338fn apply_decimal_cast<I: DecimalType, O: DecimalType>(
339    array: &PrimitiveArray<I>,
340    output_precision: u8,
341    output_scale: i8,
342    f_fallible: impl Fn(I::Native) -> Option<O::Native>,
343    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
344    cast_options: &CastOptions,
345) -> Result<PrimitiveArray<O>, ArrowError>
346where
347    I::Native: DecimalCast + ArrowNativeTypeOp,
348    O::Native: DecimalCast + ArrowNativeTypeOp,
349{
350    let array = if let Some(f_infallible) = f_infallible {
351        array.unary(f_infallible)
352    } else if cast_options.safe {
353        array.unary_opt(|x| {
354            f_fallible(x).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
355        })
356    } else {
357        let error = cast_decimal_to_decimal_error::<I, O>(output_precision, output_scale);
358        array.try_unary(|x| {
359            let v = f_fallible(x).ok_or_else(|| error(x))?;
360            O::validate_decimal_precision(v, output_precision, output_scale).map(|()| v)
361        })?
362    };
363    Ok(array)
364}
365
366fn convert_to_smaller_scale_decimal<I, O>(
367    array: &PrimitiveArray<I>,
368    input_precision: u8,
369    input_scale: i8,
370    output_precision: u8,
371    output_scale: i8,
372    cast_options: &CastOptions,
373) -> Result<PrimitiveArray<O>, ArrowError>
374where
375    I: DecimalType,
376    O: DecimalType,
377    I::Native: DecimalCast + ArrowNativeTypeOp,
378    O::Native: DecimalCast + ArrowNativeTypeOp,
379{
380    if let Some((f_fallible, f_infallible)) =
381        make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
382    {
383        apply_decimal_cast(
384            array,
385            output_precision,
386            output_scale,
387            f_fallible,
388            f_infallible,
389            cast_options,
390        )
391    } else {
392        // Scale reduction exceeds supported precision; result mathematically rounds to zero
393        let zeros = vec![O::Native::ZERO; array.len()];
394        Ok(PrimitiveArray::new(zeros.into(), array.nulls().cloned()))
395    }
396}
397
398fn convert_to_bigger_or_equal_scale_decimal<I, O>(
399    array: &PrimitiveArray<I>,
400    input_precision: u8,
401    input_scale: i8,
402    output_precision: u8,
403    output_scale: i8,
404    cast_options: &CastOptions,
405) -> Result<PrimitiveArray<O>, ArrowError>
406where
407    I: DecimalType,
408    O: DecimalType,
409    I::Native: DecimalCast + ArrowNativeTypeOp,
410    O::Native: DecimalCast + ArrowNativeTypeOp,
411{
412    if let Some((f, f_infallible)) =
413        make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
414    {
415        apply_decimal_cast(
416            array,
417            output_precision,
418            output_scale,
419            f,
420            f_infallible,
421            cast_options,
422        )
423    } else {
424        // Scale increase exceeds supported precision; return overflow error
425        Err(ArrowError::CastError(format!(
426            "Cannot cast to {}({}, {}). Value overflows for output scale",
427            O::PREFIX,
428            output_precision,
429            output_scale
430        )))
431    }
432}
433
434// Only support one type of decimal cast operations
435pub(crate) fn cast_decimal_to_decimal_same_type<T>(
436    array: &PrimitiveArray<T>,
437    input_precision: u8,
438    input_scale: i8,
439    output_precision: u8,
440    output_scale: i8,
441    cast_options: &CastOptions,
442) -> Result<ArrayRef, ArrowError>
443where
444    T: DecimalType,
445    T::Native: DecimalCast + ArrowNativeTypeOp,
446{
447    let array: PrimitiveArray<T> =
448        if input_scale == output_scale && input_precision <= output_precision {
449            array.clone()
450        } else if input_scale <= output_scale {
451            convert_to_bigger_or_equal_scale_decimal::<T, T>(
452                array,
453                input_precision,
454                input_scale,
455                output_precision,
456                output_scale,
457                cast_options,
458            )?
459        } else {
460            // input_scale > output_scale
461            convert_to_smaller_scale_decimal::<T, T>(
462                array,
463                input_precision,
464                input_scale,
465                output_precision,
466                output_scale,
467                cast_options,
468            )?
469        };
470
471    Ok(Arc::new(array.with_precision_and_scale(
472        output_precision,
473        output_scale,
474    )?))
475}
476
477// Support two different types of decimal cast operations
478pub(crate) fn cast_decimal_to_decimal<I, O>(
479    array: &PrimitiveArray<I>,
480    input_precision: u8,
481    input_scale: i8,
482    output_precision: u8,
483    output_scale: i8,
484    cast_options: &CastOptions,
485) -> Result<ArrayRef, ArrowError>
486where
487    I: DecimalType,
488    O: DecimalType,
489    I::Native: DecimalCast + ArrowNativeTypeOp,
490    O::Native: DecimalCast + ArrowNativeTypeOp,
491{
492    let array: PrimitiveArray<O> = if input_scale > output_scale {
493        convert_to_smaller_scale_decimal::<I, O>(
494            array,
495            input_precision,
496            input_scale,
497            output_precision,
498            output_scale,
499            cast_options,
500        )?
501    } else {
502        convert_to_bigger_or_equal_scale_decimal::<I, O>(
503            array,
504            input_precision,
505            input_scale,
506            output_precision,
507            output_scale,
508            cast_options,
509        )?
510    };
511
512    Ok(Arc::new(array.with_precision_and_scale(
513        output_precision,
514        output_scale,
515    )?))
516}
517
518/// Parses the given string as a decimal with the given scale, returning the
519/// unscaled representation in the decimal type's native integer (e.g. `i32`
520/// for `Decimal32Type`, `i256` for `Decimal256Type`).
521///
522/// Returns an error if the input is not a valid decimal string, or if the
523/// scaled and rounded value does not fit the maximum precision of the decimal
524/// type. The caller is responsible for validating the result against any
525/// smaller target precision.
526#[deprecated(
527    since = "60.0.0",
528    note = "Use `arrow_cast::parse::parse_decimal` instead"
529)]
530pub fn parse_string_to_decimal_native<T: DecimalType>(
531    value_str: &str,
532    scale: usize,
533) -> Result<T::Native, ArrowError> {
534    let overflow = || {
535        ArrowError::InvalidArgumentError(format!(
536            "Cannot convert {value_str} to {}: Overflow",
537            T::PREFIX
538        ))
539    };
540    let scale = i8::try_from(scale).map_err(|_| overflow())?;
541    parse_decimal_checked::<T>(value_str, T::MAX_PRECISION, scale).map_err(|e| match e {
542        DecimalParseError::InvalidFormat => {
543            ArrowError::InvalidArgumentError(format!("Invalid decimal format: {value_str:?}"))
544        }
545        DecimalParseError::Overflow => overflow(),
546    })
547}
548
549pub(crate) fn generic_string_to_decimal_cast<'a, T, S>(
550    from: &'a S,
551    precision: u8,
552    scale: i8,
553    cast_options: &CastOptions,
554) -> Result<PrimitiveArray<T>, ArrowError>
555where
556    T: DecimalType,
557    &'a S: StringArrayType<'a>,
558{
559    if cast_options.safe {
560        let iter = from
561            .iter()
562            .map(|v| parse_decimal_checked::<T>(v?, precision, scale).ok());
563        // Benefit:
564        //     15-19% faster than appending to a PrimitiveBuilder (measured
565        //     with the cast_kernels string-to-decimal benchmarks)
566        // Soundness:
567        //     The iterator is trustedLen because it comes from a `StringArray`.
568        Ok(unsafe {
569            PrimitiveArray::<T>::from_trusted_len_iter(iter)
570                .with_precision_and_scale(precision, scale)?
571        })
572    } else {
573        let mut builder = PrimitiveBuilder::<T>::with_capacity(from.len());
574        for v in from.iter() {
575            match v {
576                Some(v) => {
577                    let v = parse_decimal_checked::<T>(v, precision, scale).map_err(|e| {
578                        let reason = match e {
579                            DecimalParseError::InvalidFormat => "invalid decimal format",
580                            DecimalParseError::Overflow => "value does not fit",
581                        };
582                        ArrowError::CastError(format!(
583                            "Cannot cast string '{v}' to value of {}({precision}, {scale}) type: {reason}",
584                            T::PREFIX,
585                        ))
586                    })?;
587                    builder.append_value(v);
588                }
589                None => builder.append_null(),
590            }
591        }
592        builder.finish().with_precision_and_scale(precision, scale)
593    }
594}
595
596pub(crate) fn string_to_decimal_cast<T: DecimalType, Offset: OffsetSizeTrait>(
597    from: &GenericStringArray<Offset>,
598    precision: u8,
599    scale: i8,
600    cast_options: &CastOptions,
601) -> Result<PrimitiveArray<T>, ArrowError> {
602    generic_string_to_decimal_cast::<T, GenericStringArray<Offset>>(
603        from,
604        precision,
605        scale,
606        cast_options,
607    )
608}
609
610pub(crate) fn string_view_to_decimal_cast<T: DecimalType>(
611    from: &StringViewArray,
612    precision: u8,
613    scale: i8,
614    cast_options: &CastOptions,
615) -> Result<PrimitiveArray<T>, ArrowError> {
616    generic_string_to_decimal_cast::<T, StringViewArray>(from, precision, scale, cast_options)
617}
618
619/// Cast Utf8 to decimal
620pub(crate) fn cast_string_to_decimal<T: DecimalType, Offset: OffsetSizeTrait>(
621    from: &dyn Array,
622    precision: u8,
623    scale: i8,
624    cast_options: &CastOptions,
625) -> Result<ArrayRef, ArrowError> {
626    validate_decimal_precision_and_scale::<T>(precision, scale)?;
627
628    let result = match from.data_type() {
629        DataType::Utf8View => string_view_to_decimal_cast::<T>(
630            from.as_any().downcast_ref::<StringViewArray>().unwrap(),
631            precision,
632            scale,
633            cast_options,
634        )?,
635        DataType::Utf8 | DataType::LargeUtf8 => string_to_decimal_cast::<T, Offset>(
636            from.as_any()
637                .downcast_ref::<GenericStringArray<Offset>>()
638                .unwrap(),
639            precision,
640            scale,
641            cast_options,
642        )?,
643        _ => unreachable!(
644            "cast_to_decimal only calls cast_string_to_decimal for Utf8, LargeUtf8, or Utf8View"
645        ),
646    };
647
648    Ok(Arc::new(result))
649}
650
651pub(crate) fn cast_floating_point_to_decimal<T: ArrowPrimitiveType, D>(
652    array: &PrimitiveArray<T>,
653    precision: u8,
654    scale: i8,
655    cast_options: &CastOptions,
656) -> Result<ArrayRef, ArrowError>
657where
658    <T as ArrowPrimitiveType>::Native: AsPrimitive<f64>,
659    D: DecimalType + ArrowPrimitiveType,
660    <D as ArrowPrimitiveType>::Native: DecimalCast,
661{
662    validate_decimal_precision_and_scale::<D>(precision, scale)?;
663    let mul = 10_f64.powi(scale as i32);
664
665    if cast_options.safe {
666        array
667            .unary_opt::<_, D>(|v| float_to_decimal_checked::<D>(v.as_(), mul, precision).ok())
668            .with_precision_and_scale(precision, scale)
669            .map(|a| Arc::new(a) as ArrayRef)
670    } else {
671        array
672            .try_unary::<_, D, _>(|v| {
673                float_to_decimal_checked::<D>(v.as_(), mul, precision)
674                    .map_err(|e| e.into_arrow_error(v, precision, scale))
675            })?
676            .with_precision_and_scale(precision, scale)
677            .map(|a| Arc::new(a) as ArrayRef)
678    }
679}
680
681/// Converts a floating point value to the unscaled integer representation of a decimal.
682///
683/// Scales the input by `10^scale` and rounds half away from zero. Returns an error
684/// if the precision or scale is invalid, or the result does not fit the native
685/// type or the requested precision. Non-finite inputs are rejected.
686/// Use [`float_to_decimal_opt`] when the reason for failure is not needed.
687///
688/// ```
689/// use arrow_array::types::Decimal32Type;
690/// use arrow_cast::cast::float_to_decimal;
691///
692/// assert_eq!(float_to_decimal::<Decimal32Type>(12.345, 5, 2).unwrap(), 1235);
693/// assert!(float_to_decimal::<Decimal32Type>(12345.678, 5, 2).is_err());
694/// ```
695#[inline]
696pub fn float_to_decimal<D>(input: f64, precision: u8, scale: i8) -> Result<D::Native, ArrowError>
697where
698    D: DecimalType,
699    D::Native: DecimalCast,
700{
701    validate_decimal_precision_and_scale::<D>(precision, scale)?;
702    float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision)
703        .map_err(|e| e.into_arrow_error(input, precision, scale))
704}
705
706/// Like [`float_to_decimal`], but returns `None` on failure.
707/// With valid precision and scale, conversion failures do not allocate an error message.
708///
709/// ```
710/// use arrow_array::types::Decimal32Type;
711/// use arrow_cast::cast::float_to_decimal_opt;
712///
713/// assert_eq!(float_to_decimal_opt::<Decimal32Type>(12.345, 5, 2), Some(1235));
714/// assert_eq!(float_to_decimal_opt::<Decimal32Type>(12345.678, 5, 2), None);
715/// ```
716pub fn float_to_decimal_opt<D>(input: f64, precision: u8, scale: i8) -> Option<D::Native>
717where
718    D: DecimalType,
719    D::Native: DecimalCast,
720{
721    validate_decimal_precision_and_scale::<D>(precision, scale).ok()?;
722    float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision).ok()
723}
724
725// Keep failure reporting allocation-free for scalar Option and safe array casts.
726enum FloatToDecimalError<D: DecimalType> {
727    NativeOverflow,
728    PrecisionOverflow(D::Native),
729}
730
731impl<D: DecimalType> FloatToDecimalError<D> {
732    fn into_arrow_error(self, input: impl std::fmt::Debug, precision: u8, scale: i8) -> ArrowError {
733        match self {
734            Self::NativeOverflow => ArrowError::CastError(format!(
735                "Cannot cast to {}({}, {}). Overflowing on {:?}",
736                D::PREFIX,
737                precision,
738                scale,
739                input
740            )),
741            Self::PrecisionOverflow(value) => {
742                D::validate_decimal_precision(value, precision, scale).unwrap_err()
743            }
744        }
745    }
746}
747
748// `mul` is precomputed once for array casts. Precision must already be validated.
749#[inline]
750fn float_to_decimal_checked<D>(
751    input: f64,
752    mul: f64,
753    precision: u8,
754) -> Result<D::Native, FloatToDecimalError<D>>
755where
756    D: DecimalType,
757    D::Native: DecimalCast,
758{
759    let value =
760        D::Native::from_f64((mul * input).round()).ok_or(FloatToDecimalError::NativeOverflow)?;
761    if D::is_valid_decimal_precision(value, precision) {
762        Ok(value)
763    } else {
764        Err(FloatToDecimalError::PrecisionOverflow(value))
765    }
766}
767
768/// Cast a single floating point value to a decimal native with the given multiple.
769/// Returns `None` if the scaled and rounded value does not fit the native integer
770/// type. The caller must separately validate the decimal precision.
771#[deprecated(since = "60.0.0", note = "Use `float_to_decimal` instead")]
772#[inline(always)]
773pub fn single_float_to_decimal<D>(input: f64, mul: f64) -> Option<D::Native>
774where
775    D: DecimalType + ArrowPrimitiveType,
776    <D as ArrowPrimitiveType>::Native: DecimalCast,
777{
778    D::Native::from_f64((mul * input).round())
779}
780
781pub(crate) fn cast_decimal_to_integer<D, T>(
782    array: &dyn Array,
783    scale: i8,
784    cast_options: &CastOptions,
785) -> Result<ArrayRef, ArrowError>
786where
787    T: ArrowPrimitiveType,
788    <T as ArrowPrimitiveType>::Native: NumCast,
789    D: DecimalType + ArrowPrimitiveType,
790    <D as ArrowPrimitiveType>::Native: ToPrimitive,
791{
792    let array = array.as_primitive::<D>();
793
794    // div is always > 0, so div_wrapping(div) can never overflow/panic
795    let div: D::Native = D::Native::usize_as(10)
796        .pow_checked(scale.unsigned_abs() as u32)
797        .map_err(|_| {
798            ArrowError::CastError(format!(
799                "Cannot cast to {:?}. The scale {} causes overflow.",
800                D::PREFIX,
801                scale,
802            ))
803        })?;
804
805    let mut value_builder = PrimitiveBuilder::<T>::with_capacity(array.len());
806
807    if scale < 0 {
808        match cast_options.safe {
809            true => {
810                for i in 0..array.len() {
811                    if array.is_null(i) {
812                        value_builder.append_null();
813                    } else {
814                        let v = array
815                            .value(i)
816                            .mul_checked(div)
817                            .ok()
818                            .and_then(<T::Native as NumCast>::from::<D::Native>);
819                        value_builder.append_option(v);
820                    }
821                }
822            }
823            false => {
824                for i in 0..array.len() {
825                    if array.is_null(i) {
826                        value_builder.append_null();
827                    } else {
828                        let v = array.value(i).mul_checked(div)?;
829
830                        let value =
831                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
832                                ArrowError::CastError(format!(
833                                    "value of {:?} is out of range {}",
834                                    v,
835                                    T::DATA_TYPE
836                                ))
837                            })?;
838
839                        value_builder.append_value(value);
840                    }
841                }
842            }
843        }
844    } else {
845        match cast_options.safe {
846            true => {
847                for i in 0..array.len() {
848                    if array.is_null(i) {
849                        value_builder.append_null();
850                    } else {
851                        let v = array.value(i).div_wrapping(div);
852                        let v = <T::Native as NumCast>::from(v);
853                        value_builder.append_option(v);
854                    }
855                }
856            }
857            false => {
858                for i in 0..array.len() {
859                    if array.is_null(i) {
860                        value_builder.append_null();
861                    } else {
862                        let v = array.value(i).div_wrapping(div);
863
864                        let value =
865                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
866                                ArrowError::CastError(format!(
867                                    "value of {:?} is out of range {}",
868                                    v,
869                                    T::DATA_TYPE
870                                ))
871                            })?;
872
873                        value_builder.append_value(value);
874                    }
875                }
876            }
877        }
878    }
879    Ok(Arc::new(value_builder.finish()))
880}
881
882/// Cast a decimal array to a floating point array.
883///
884/// Conversion is lossy and follows standard floating point semantics. Values
885/// that exceed the representable range become `INFINITY` or `-INFINITY` without
886/// returning an error.
887pub(crate) fn cast_decimal_to_float<D: DecimalType, T: ArrowPrimitiveType, F>(
888    array: &dyn Array,
889    op: F,
890) -> Result<ArrayRef, ArrowError>
891where
892    F: Fn(D::Native) -> T::Native,
893{
894    let array = array.as_primitive::<D>();
895    let array = array.unary::<_, T>(op);
896    Ok(Arc::new(array))
897}
898
899/// Convert an integer to a decimal native value without wrapping.
900///
901/// `AsPrimitive` / `as` silently truncates when the source is wider than `M`
902/// (for example `5_000_000_000i64 as i32`). All integer sources fit in `i128`
903/// losslessly, which [`DecimalCast`] then converts to the decimal native type
904/// with a range check. For types that always fit (e.g. `i64` to `Decimal128`) this
905/// should get optimized to being equivalent to `i64 as i128`.
906fn integer_to_decimal_native<I, M>(value: I) -> Option<M>
907where
908    I: Into<i128>,
909    M: DecimalCast,
910{
911    M::from_decimal(value.into())
912}
913
914/// Returns true if every value of `array` has at most `digits` decimal digits.
915///
916/// If the source type cannot hold that many digits, every value fits;
917/// otherwise the data is scanned, including null slots, since `unary`
918/// converts those as well.
919fn integers_fit_digits<T>(array: &PrimitiveArray<T>, digits: u32) -> bool
920where
921    T: ArrowPrimitiveType,
922    T::Native: ArrowNativeTypeOp,
923{
924    let Ok(bound) = T::Native::usize_as(10).pow_checked(digits) else {
925        // The type's maximum is below the bound, and its minimum, one larger
926        // in magnitude, is a power of two that no power of ten can equal.
927        return true;
928    };
929    let hi = bound.sub_wrapping(T::Native::ONE);
930    // Unsigned types have no negative values to bound.
931    let lo = T::Native::ZERO.sub_checked(hi).unwrap_or(T::Native::ZERO);
932
933    // The scan works in chunks, so an array that does not fit is rejected after
934    // its first failing chunk. Each chunk is checked without branching, so it
935    // vectorizes.
936    array
937        .values()
938        .chunks(64)
939        .all(|chunk| chunk.iter().fold(true, |ok, &v| ok & (v >= lo) & (v <= hi)))
940}
941
942fn cast_integer_to_decimal<
943    T: ArrowPrimitiveType,
944    D: DecimalType + ArrowPrimitiveType<Native = M>,
945    M,
946>(
947    array: &PrimitiveArray<T>,
948    precision: u8,
949    scale: i8,
950    cast_options: &CastOptions,
951) -> Result<ArrayRef, ArrowError>
952where
953    <T as ArrowPrimitiveType>::Native: ArrowNativeTypeOp + Into<i128>,
954    M: ArrowNativeTypeOp + DecimalCast,
955{
956    let overflow = |v: T::Native| {
957        ArrowError::CastError(format!(
958            "Cannot cast to {}({precision}, {scale}). Overflowing on {v:?}",
959            D::PREFIX,
960        ))
961    };
962
963    // An invalid precision or scale would let the bound below admit values the
964    // decimal native type cannot hold, so reject it before choosing a kernel.
965    validate_decimal_precision_and_scale::<D>(precision, scale)?;
966
967    // Scan for values that don't fit in the target type before choosing a
968    // kernel: in the common case that all values fit, the cast is infallible
969    // and we can use the `unary` kernel, which is very fast. Empirically, the
970    // cost of doing the out-of-range scan is small relative to the win from
971    // using a faster kernel.
972    //
973    // A value fits when it has at most `precision - scale` decimal digits: a
974    // nonnegative scale appends that many trailing zeros, and a negative scale
975    // removes digits first. Validation guarantees the difference is not
976    // negative.
977    let digits = (precision as i32 - scale as i32) as u32;
978    let all_fit = integers_fit_digits(array, digits);
979
980    let array = if scale < 0 {
981        // Compute the scale factor once in the source type. Scaling before the
982        // checked conversion permits values that only fit the decimal native
983        // type after scaling.
984        //
985        // scale_factor is always > 0, so div_wrapping(scale_factor) can never
986        // overflow/panic
987        let scale_factor = T::Native::usize_as(10)
988            .pow_checked(scale.unsigned_abs() as u32)
989            .ok();
990
991        match (scale_factor, cast_options.safe) {
992            (Some(scale_factor), _) if all_fit => array.unary::<_, D>(|v| {
993                integer_to_decimal_native::<_, M>(v.div_wrapping(scale_factor))
994                    .expect("value fits the decimal")
995            }),
996            (Some(scale_factor), true) => array.unary_opt::<_, D>(|v| {
997                let v = integer_to_decimal_native::<_, M>(v.div_wrapping(scale_factor))?;
998                (D::is_valid_decimal_precision(v, precision)).then_some(v)
999            }),
1000            (Some(scale_factor), false) => array.try_unary::<_, D, _>(|v| {
1001                let v = integer_to_decimal_native::<_, M>(v.div_wrapping(scale_factor))
1002                    .ok_or_else(|| overflow(v))?;
1003                D::validate_decimal_precision(v, precision, scale).map(|()| v)
1004            })?,
1005            // A scale factor that overflows the source type is larger than all
1006            // source values, so integer division produces zero.
1007            //
1008            // For a well formed decimal scale, this path should never be reachable.
1009            (None, _) => array.unary::<_, D>(|_| M::ZERO),
1010        }
1011    } else {
1012        let scale_factor = M::usize_as(10)
1013            .pow_checked(scale.unsigned_abs() as u32)
1014            .map_err(|_| {
1015                ArrowError::CastError(format!(
1016                    "Cannot cast to {:?}({}, {}). The scale causes overflow.",
1017                    D::PREFIX,
1018                    precision,
1019                    scale,
1020                ))
1021            })?;
1022
1023        if all_fit {
1024            array.unary::<_, D>(|v| {
1025                integer_to_decimal_native::<_, M>(v)
1026                    .expect("value fits the decimal")
1027                    .mul_wrapping(scale_factor)
1028            })
1029        } else if cast_options.safe {
1030            array.unary_opt::<_, D>(|v| {
1031                let v = integer_to_decimal_native::<_, M>(v)
1032                    .and_then(|v| v.mul_checked(scale_factor).ok())?;
1033                (D::is_valid_decimal_precision(v, precision)).then_some(v)
1034            })
1035        } else {
1036            array.try_unary::<_, D, _>(|v| {
1037                let v = integer_to_decimal_native::<_, M>(v)
1038                    .ok_or_else(|| overflow(v))
1039                    .and_then(|v| v.mul_checked(scale_factor))?;
1040                D::validate_decimal_precision(v, precision, scale).map(|()| v)
1041            })?
1042        }
1043    };
1044
1045    Ok(Arc::new(array.with_precision_and_scale(precision, scale)?))
1046}
1047
1048pub(crate) fn cast_from_decimal<D, F>(
1049    array: &dyn Array,
1050    scale: &i8,
1051    from_type: &DataType,
1052    to_type: &DataType,
1053    as_float: F,
1054    cast_options: &CastOptions,
1055) -> Result<ArrayRef, ArrowError>
1056where
1057    D: DecimalType + ArrowPrimitiveType,
1058    <D as ArrowPrimitiveType>::Native: ToPrimitive,
1059    F: Fn(D::Native) -> f64,
1060{
1061    use DataType::*;
1062    // cast decimal to other type
1063    match to_type {
1064        UInt8 => cast_decimal_to_integer::<D, UInt8Type>(array, *scale, cast_options),
1065        UInt16 => cast_decimal_to_integer::<D, UInt16Type>(array, *scale, cast_options),
1066        UInt32 => cast_decimal_to_integer::<D, UInt32Type>(array, *scale, cast_options),
1067        UInt64 => cast_decimal_to_integer::<D, UInt64Type>(array, *scale, cast_options),
1068        Int8 => cast_decimal_to_integer::<D, Int8Type>(array, *scale, cast_options),
1069        Int16 => cast_decimal_to_integer::<D, Int16Type>(array, *scale, cast_options),
1070        Int32 => cast_decimal_to_integer::<D, Int32Type>(array, *scale, cast_options),
1071        Int64 => cast_decimal_to_integer::<D, Int64Type>(array, *scale, cast_options),
1072        Float16 => cast_decimal_to_float::<D, Float16Type, _>(array, |x| {
1073            half::f16::from_f64(single_decimal_to_float_lossy::<D, F>(
1074                &as_float,
1075                x,
1076                <i32 as From<i8>>::from(*scale),
1077            ))
1078        }),
1079        Float32 => cast_decimal_to_f32::<D, F>(array, &as_float, *scale),
1080        Float64 => cast_decimal_to_f64::<D, F>(array, &as_float, *scale),
1081        Utf8View => value_to_string_view(array, cast_options),
1082        Utf8 => value_to_string::<i32>(array, cast_options),
1083        LargeUtf8 => value_to_string::<i64>(array, cast_options),
1084        Null => Ok(new_null_array(to_type, array.len())),
1085        _ => Err(ArrowError::InvalidArgumentError(format!(
1086            "Casting from {from_type} to {to_type} not supported"
1087        ))),
1088    }
1089}
1090
1091pub(crate) fn cast_to_decimal<D, M>(
1092    array: &dyn Array,
1093    precision: &u8,
1094    scale: &i8,
1095    from_type: &DataType,
1096    to_type: &DataType,
1097    cast_options: &CastOptions,
1098) -> Result<ArrayRef, ArrowError>
1099where
1100    D: DecimalType + ArrowPrimitiveType<Native = M>,
1101    M: ArrowNativeTypeOp + DecimalCast,
1102{
1103    use DataType::*;
1104    // cast data to decimal
1105    match from_type {
1106        UInt8 => cast_integer_to_decimal::<_, D, M>(
1107            array.as_primitive::<UInt8Type>(),
1108            *precision,
1109            *scale,
1110            cast_options,
1111        ),
1112        UInt16 => cast_integer_to_decimal::<_, D, _>(
1113            array.as_primitive::<UInt16Type>(),
1114            *precision,
1115            *scale,
1116            cast_options,
1117        ),
1118        UInt32 => cast_integer_to_decimal::<_, D, _>(
1119            array.as_primitive::<UInt32Type>(),
1120            *precision,
1121            *scale,
1122            cast_options,
1123        ),
1124        UInt64 => cast_integer_to_decimal::<_, D, _>(
1125            array.as_primitive::<UInt64Type>(),
1126            *precision,
1127            *scale,
1128            cast_options,
1129        ),
1130        Int8 => cast_integer_to_decimal::<_, D, _>(
1131            array.as_primitive::<Int8Type>(),
1132            *precision,
1133            *scale,
1134            cast_options,
1135        ),
1136        Int16 => cast_integer_to_decimal::<_, D, _>(
1137            array.as_primitive::<Int16Type>(),
1138            *precision,
1139            *scale,
1140            cast_options,
1141        ),
1142        Int32 => cast_integer_to_decimal::<_, D, _>(
1143            array.as_primitive::<Int32Type>(),
1144            *precision,
1145            *scale,
1146            cast_options,
1147        ),
1148        Int64 => cast_integer_to_decimal::<_, D, _>(
1149            array.as_primitive::<Int64Type>(),
1150            *precision,
1151            *scale,
1152            cast_options,
1153        ),
1154        Float16 => cast_floating_point_to_decimal::<_, D>(
1155            array.as_primitive::<Float16Type>(),
1156            *precision,
1157            *scale,
1158            cast_options,
1159        ),
1160        Float32 => cast_floating_point_to_decimal::<_, D>(
1161            array.as_primitive::<Float32Type>(),
1162            *precision,
1163            *scale,
1164            cast_options,
1165        ),
1166        Float64 => cast_floating_point_to_decimal::<_, D>(
1167            array.as_primitive::<Float64Type>(),
1168            *precision,
1169            *scale,
1170            cast_options,
1171        ),
1172        Utf8View | Utf8 => {
1173            cast_string_to_decimal::<D, i32>(array, *precision, *scale, cast_options)
1174        }
1175        LargeUtf8 => cast_string_to_decimal::<D, i64>(array, *precision, *scale, cast_options),
1176        Null => Ok(new_null_array(to_type, array.len())),
1177        _ => Err(ArrowError::InvalidArgumentError(format!(
1178            "Casting from {from_type} to {to_type} not supported"
1179        ))),
1180    }
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185    use super::*;
1186
1187    #[test]
1188    fn test_float_to_decimal() {
1189        fn check<D: DecimalType>()
1190        where
1191            D::Native: DecimalCast,
1192        {
1193            for (input, scale, expected) in [
1194                (12.345, 2, 1235),
1195                (-12.345, 2, -1235),
1196                (999.99, 2, 99999),
1197                (-999.99, 2, -99999),
1198                (99999.25, 0, 99999),
1199                (999992.5, -1, 99999),
1200                (0.0, 2, 0),
1201            ] {
1202                assert_eq!(
1203                    float_to_decimal_opt::<D>(input, 5, scale),
1204                    float_to_decimal::<D>(input, 5, scale).ok(),
1205                );
1206                assert_eq!(
1207                    float_to_decimal::<D>(input, 5, scale)
1208                        .unwrap()
1209                        .to_i128()
1210                        .unwrap(),
1211                    expected,
1212                );
1213            }
1214            for (input, scale) in [
1215                (12345.678, 2),
1216                (-12345.678, 2),
1217                (999.9975, 2),
1218                (-999.9975, 2),
1219                (99999.75, 0),
1220                (-99999.75, 0),
1221                (999997.5, -1),
1222                (-999997.5, -1),
1223                (f64::NAN, 0),
1224                (f64::INFINITY, 0),
1225                (f64::NEG_INFINITY, 0),
1226                (f64::MAX, 0),
1227            ] {
1228                assert!(float_to_decimal::<D>(input, 5, scale).is_err());
1229                assert!(float_to_decimal_opt::<D>(input, 5, scale).is_none());
1230            }
1231            for (precision, scale) in [(0, 0), (D::MAX_PRECISION + 1, 0), (5, 6)] {
1232                assert!(float_to_decimal::<D>(0.0, precision, scale).is_err());
1233                assert!(float_to_decimal_opt::<D>(0.0, precision, scale).is_none());
1234            }
1235        }
1236        check::<Decimal32Type>();
1237        check::<Decimal64Type>();
1238        check::<Decimal128Type>();
1239        check::<Decimal256Type>();
1240    }
1241
1242    #[test]
1243    #[expect(deprecated)]
1244    fn test_single_float_to_decimal_preserves_native_range() {
1245        // Deprecation preserves acceptance of values that fit i32 but exceed
1246        // Decimal32's maximum precision. The new API checks that precision.
1247        for (input, expected) in [
1248            (1_000_000_000.0, 1_000_000_000),
1249            (-1_000_000_000.0, -1_000_000_000),
1250            (999_999_999.75, 1_000_000_000),
1251            (-999_999_999.75, -1_000_000_000),
1252        ] {
1253            assert_eq!(
1254                single_float_to_decimal::<Decimal32Type>(input, 1.0),
1255                Some(expected)
1256            );
1257            assert!(float_to_decimal::<Decimal32Type>(input, 9, 0).is_err());
1258        }
1259        assert_eq!(
1260            single_float_to_decimal::<Decimal32Type>(999_999_999.0, 1.0),
1261            Some(999_999_999),
1262        );
1263        assert_eq!(
1264            single_float_to_decimal::<Decimal32Type>(12.345, 100.0),
1265            Some(1235)
1266        );
1267        assert_eq!(
1268            single_float_to_decimal::<Decimal32Type>(f64::MAX, 1.0),
1269            None
1270        );
1271    }
1272
1273    #[test]
1274    #[expect(deprecated)]
1275    fn test_parse_string_to_decimal_native() {
1276        assert_eq!(
1277            parse_string_to_decimal_native::<Decimal128Type>("123.456", 2).unwrap(),
1278            12346
1279        );
1280        // The value is checked against the maximum precision of the type, not
1281        // against any target precision
1282        assert_eq!(
1283            parse_string_to_decimal_native::<Decimal128Type>(&"9".repeat(38), 0).unwrap(),
1284            10_i128.pow(38) - 1
1285        );
1286        assert!(
1287            parse_string_to_decimal_native::<Decimal128Type>(&i128::MAX.to_string(), 0).is_err()
1288        );
1289        assert_eq!(
1290            parse_string_to_decimal_native::<Decimal128Type>("abc", 2)
1291                .unwrap_err()
1292                .to_string(),
1293            "Invalid argument error: Invalid decimal format: \"abc\""
1294        );
1295        assert_eq!(
1296            parse_string_to_decimal_native::<Decimal32Type>("1", 10)
1297                .unwrap_err()
1298                .to_string(),
1299            "Invalid argument error: Cannot convert 1 to Decimal32: Overflow"
1300        );
1301    }
1302
1303    #[test]
1304    fn test_rescale_decimal_upscale_within_precision() {
1305        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(
1306            12_345_i128, // 123.45 with scale 2
1307            5,
1308            2,
1309            8,
1310            5,
1311        );
1312        assert_eq!(result, Some(12_345_000_i128));
1313    }
1314
1315    #[test]
1316    fn test_rescale_decimal_downscale_rounds_half_away_from_zero() {
1317        let positive = rescale_decimal::<Decimal128Type, Decimal128Type>(
1318            1_050_i128, // 1.050 with scale 3
1319            5, 3, 5, 1,
1320        );
1321        assert_eq!(positive, Some(11_i128)); // 1.1 with scale 1
1322
1323        let negative = rescale_decimal::<Decimal128Type, Decimal128Type>(
1324            -1_050_i128, // -1.050 with scale 3
1325            5,
1326            3,
1327            5,
1328            1,
1329        );
1330        assert_eq!(negative, Some(-11_i128)); // -1.1 with scale 1
1331    }
1332
1333    #[test]
1334    fn test_rescale_decimal_downscale_large_delta_returns_zero() {
1335        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(12_345_i32, 9, 9, 9, 4);
1336        assert_eq!(result, Some(0_i32));
1337    }
1338
1339    #[test]
1340    fn test_rescale_decimal_upscale_overflow_returns_none() {
1341        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(9_999_i32, 4, 0, 5, 2);
1342        assert_eq!(result, None);
1343    }
1344
1345    #[test]
1346    fn test_rescale_decimal256_extreme_scales() {
1347        assert_eq!(
1348            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, -76, 76, 0),
1349            None
1350        );
1351        assert_eq!(
1352            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ZERO, 76, -76, 76, 0),
1353            Some(i256::ZERO)
1354        );
1355        assert_eq!(
1356            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, i8::MIN, 76, 76),
1357            None
1358        );
1359        assert_eq!(
1360            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, 76, 76, i8::MIN),
1361            Some(i256::ZERO)
1362        );
1363    }
1364
1365    #[test]
1366    fn test_rescale_decimal_invalid_input_precision_scale_returns_none() {
1367        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 39, 39, 38, 38);
1368        assert_eq!(result, None);
1369    }
1370
1371    #[test]
1372    fn test_rescale_decimal_invalid_output_precision_scale_returns_none() {
1373        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 38, 38, 39, 39);
1374        assert_eq!(result, None);
1375    }
1376}