Skip to main content

arrow_cast/cast/
decimal.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::cast::*;
19use crate::parse::{DecimalParseError, parse_decimal_checked};
20
21/// A utility trait that provides checked conversions between
22/// decimal types inspired by [`NumCast`]
23pub trait DecimalCast: Sized {
24    /// Convert the decimal to an i32
25    fn to_i32(self) -> Option<i32>;
26
27    /// Convert the decimal to an i64
28    fn to_i64(self) -> Option<i64>;
29
30    /// Convert the decimal to an i128
31    fn to_i128(self) -> Option<i128>;
32
33    /// Convert the decimal to an i256
34    fn to_i256(self) -> Option<i256>;
35
36    /// Convert a decimal from a decimal
37    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self>;
38
39    /// Convert a decimal from a f64
40    fn from_f64(n: f64) -> Option<Self>;
41}
42
43impl DecimalCast for i32 {
44    fn to_i32(self) -> Option<i32> {
45        Some(self)
46    }
47
48    fn to_i64(self) -> Option<i64> {
49        Some(self as i64)
50    }
51
52    fn to_i128(self) -> Option<i128> {
53        Some(self as i128)
54    }
55
56    fn to_i256(self) -> Option<i256> {
57        Some(i256::from_i128(self as i128))
58    }
59
60    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
61        n.to_i32()
62    }
63
64    fn from_f64(n: f64) -> Option<Self> {
65        n.to_i32()
66    }
67}
68
69impl DecimalCast for i64 {
70    fn to_i32(self) -> Option<i32> {
71        i32::try_from(self).ok()
72    }
73
74    fn to_i64(self) -> Option<i64> {
75        Some(self)
76    }
77
78    fn to_i128(self) -> Option<i128> {
79        Some(self as i128)
80    }
81
82    fn to_i256(self) -> Option<i256> {
83        Some(i256::from_i128(self as i128))
84    }
85
86    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
87        n.to_i64()
88    }
89
90    fn from_f64(n: f64) -> Option<Self> {
91        // Call implementation explicitly otherwise this resolves to `to_i64`
92        // in arrow-buffer that behaves differently.
93        num_traits::ToPrimitive::to_i64(&n)
94    }
95}
96
97impl DecimalCast for i128 {
98    fn to_i32(self) -> Option<i32> {
99        i32::try_from(self).ok()
100    }
101
102    fn to_i64(self) -> Option<i64> {
103        i64::try_from(self).ok()
104    }
105
106    fn to_i128(self) -> Option<i128> {
107        Some(self)
108    }
109
110    fn to_i256(self) -> Option<i256> {
111        Some(i256::from_i128(self))
112    }
113
114    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
115        n.to_i128()
116    }
117
118    fn from_f64(n: f64) -> Option<Self> {
119        n.to_i128()
120    }
121}
122
123impl DecimalCast for i256 {
124    fn to_i32(self) -> Option<i32> {
125        self.to_i128().map(|x| i32::try_from(x).ok())?
126    }
127
128    fn to_i64(self) -> Option<i64> {
129        self.to_i128().map(|x| i64::try_from(x).ok())?
130    }
131
132    fn to_i128(self) -> Option<i128> {
133        self.to_i128()
134    }
135
136    fn to_i256(self) -> Option<i256> {
137        Some(self)
138    }
139
140    fn from_decimal<T: DecimalCast>(n: T) -> Option<Self> {
141        n.to_i256()
142    }
143
144    fn from_f64(n: f64) -> Option<Self> {
145        i256::from_f64(n)
146    }
147}
148
149/// Construct closures to upscale decimals from `(input_precision, input_scale)` to
150/// `(output_precision, output_scale)`.
151///
152/// Returns `(f_fallible, f_infallible)` where:
153/// * `f_fallible` yields `None` when the requested cast would overflow
154/// * `f_infallible` is present only when every input is guaranteed to succeed; otherwise it is `None`
155///   and callers must fall back to `f_fallible`
156///
157/// Returns `None` if the required scale increase `delta_scale = output_scale - input_scale`
158/// exceeds the supported precomputed precision table `O::MAX_FOR_EACH_PRECISION`.
159/// In that case, the caller should treat this as an overflow for the output scale
160/// and handle it accordingly (e.g., return a cast error).
161#[expect(clippy::type_complexity)]
162fn make_upscaler<I: DecimalType, O: DecimalType>(
163    input_precision: u8,
164    input_scale: i8,
165    output_precision: u8,
166    output_scale: i8,
167) -> Option<(
168    impl Fn(I::Native) -> Option<O::Native>,
169    Option<impl Fn(I::Native) -> O::Native>,
170)>
171where
172    I::Native: DecimalCast + ArrowNativeTypeOp,
173    O::Native: DecimalCast + ArrowNativeTypeOp,
174{
175    let delta_scale = output_scale - input_scale;
176
177    // O::MAX_FOR_EACH_PRECISION[k] stores 10^k - 1 (e.g., 9, 99, 999, ...).
178    // Adding 1 yields exactly 10^k without computing a power at runtime.
179    // Using the precomputed table avoids pow(10, k) and its checked/overflow
180    // handling, which is faster and simpler for scaling by 10^delta_scale.
181    let max = O::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
182    let mul = max.add_wrapping(O::Native::ONE);
183    let f_fallible = move |x| O::Native::from_decimal(x)?.mul_checked(mul).ok();
184
185    // if the gain in precision (digits) is greater than the multiplication due to scaling
186    // every number will fit into the output type
187    // Example: If we are starting with any number of precision 5 [xxxxx],
188    // then an increase of scale by 3 will have the following effect on the representation:
189    // [xxxxx] -> [xxxxx000], so for the cast to be infallible, the output type
190    // needs to provide at least 8 digits precision
191    let is_infallible_cast = (input_precision as i8) + delta_scale <= (output_precision as i8);
192    let f_infallible = is_infallible_cast
193        .then_some(move |x| O::Native::from_decimal(x).unwrap().mul_wrapping(mul));
194    Some((f_fallible, f_infallible))
195}
196
197/// Construct closures to downscale decimals from `(input_precision, input_scale)` to
198/// `(output_precision, output_scale)`.
199///
200/// Returns `(f_fallible, f_infallible)` where:
201/// * `f_fallible` yields `None` when the requested cast would overflow
202/// * `f_infallible` is present only when every input is guaranteed to succeed; otherwise it is `None`
203///   and callers must fall back to `f_fallible`
204///
205/// Returns `None` if the required scale reduction `delta_scale = input_scale - output_scale`
206/// exceeds the supported precomputed precision table `I::MAX_FOR_EACH_PRECISION`.
207/// In this scenario, any value would round to zero (e.g., dividing by 10^k where k exceeds the
208/// available precision). Callers should therefore produce zero values (preserving nulls) rather
209/// than returning an error.
210#[expect(clippy::type_complexity)]
211fn make_downscaler<I: DecimalType, O: DecimalType>(
212    input_precision: u8,
213    input_scale: i8,
214    output_precision: u8,
215    output_scale: i8,
216) -> Option<(
217    impl Fn(I::Native) -> Option<O::Native>,
218    Option<impl Fn(I::Native) -> O::Native>,
219)>
220where
221    I::Native: DecimalCast + ArrowNativeTypeOp,
222    O::Native: DecimalCast + ArrowNativeTypeOp,
223{
224    let delta_scale = input_scale - output_scale;
225
226    // delta_scale is guaranteed to be > 0, but may also be larger than I::MAX_PRECISION. If so, the
227    // scale change divides out more digits than the input has precision and the result of the cast
228    // is always zero. For example, if we try to apply delta_scale=10 a decimal32 value, the largest
229    // possible result is 999999999/10000000000 = 0.0999999999, which rounds to zero. Smaller values
230    // (e.g. 1/10000000000) or larger delta_scale (e.g. 999999999/10000000000000) produce even
231    // smaller results, which also round to zero. In that case, just return an array of zeros.
232    let max = I::MAX_FOR_EACH_PRECISION.get(delta_scale as usize)?;
233
234    let div = max.add_wrapping(I::Native::ONE);
235    let half = div.div_wrapping(I::Native::ONE.add_wrapping(I::Native::ONE));
236    let half_neg = half.neg_wrapping();
237
238    let f_fallible = move |x: I::Native| {
239        // div is >= 10 and so this cannot overflow
240        let d = x.div_wrapping(div);
241        let r = x.mod_wrapping(div);
242
243        // Round result
244        let adjusted = match x >= I::Native::ZERO {
245            true if r >= half => d.add_wrapping(I::Native::ONE),
246            false if r <= half_neg => d.sub_wrapping(I::Native::ONE),
247            _ => d,
248        };
249        O::Native::from_decimal(adjusted)
250    };
251
252    // if the reduction of the input number through scaling (dividing) is greater
253    // than a possible precision loss (plus potential increase via rounding)
254    // every input number will fit into the output type
255    // Example: If we are starting with any number of precision 5 [xxxxx],
256    // then and decrease the scale by 3 will have the following effect on the representation:
257    // [xxxxx] -> [xx] (+ 1 possibly, due to rounding).
258    // The rounding may add a digit, so the cast to be infallible,
259    // the output type needs to have at least 3 digits of precision.
260    // e.g. Decimal(5, 3) 99.999 to Decimal(3, 0) will result in 100:
261    // [99999] -> [99] + 1 = [100], a cast to Decimal(2, 0) would not be possible
262    let is_infallible_cast = (input_precision as i8) - delta_scale < (output_precision as i8);
263    let f_infallible = is_infallible_cast.then_some(move |x| f_fallible(x).unwrap());
264    Some((f_fallible, f_infallible))
265}
266
267/// Apply the rescaler function to the value.
268/// If the rescaler is infallible, use the infallible function.
269/// Otherwise, use the fallible function and validate the precision.
270fn apply_rescaler<I: DecimalType, O: DecimalType>(
271    value: I::Native,
272    output_precision: u8,
273    f: impl Fn(I::Native) -> Option<O::Native>,
274    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
275) -> Option<O::Native>
276where
277    I::Native: DecimalCast,
278    O::Native: DecimalCast,
279{
280    if let Some(f_infallible) = f_infallible {
281        Some(f_infallible(value))
282    } else {
283        f(value).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
284    }
285}
286
287/// Rescales a decimal value from `(input_precision, input_scale)` to
288/// `(output_precision, output_scale)` and returns the converted number when it fits
289/// within the output precision.
290///
291/// The function first validates that the requested precision and scale are supported for
292/// both the source and destination decimal types. It then either upscales (multiplying
293/// by an appropriate power of ten) or downscales (dividing with rounding) the input value.
294/// When the scaling factor exceeds the precision table of the destination type, the value
295/// is treated as an overflow for upscaling, or rounded to zero for downscaling (as any
296/// possible result would be zero at the requested scale).
297///
298/// This mirrors the column-oriented helpers of decimal casting but operates on a single value
299/// (row-level) instead of an entire array.
300///
301/// Returns `None` if the value cannot be represented with the requested precision.
302pub fn rescale_decimal<I: DecimalType, O: DecimalType>(
303    value: I::Native,
304    input_precision: u8,
305    input_scale: i8,
306    output_precision: u8,
307    output_scale: i8,
308) -> Option<O::Native>
309where
310    I::Native: DecimalCast + ArrowNativeTypeOp,
311    O::Native: DecimalCast + ArrowNativeTypeOp,
312{
313    validate_decimal_precision_and_scale::<I>(input_precision, input_scale).ok()?;
314    validate_decimal_precision_and_scale::<O>(output_precision, output_scale).ok()?;
315
316    if input_scale <= output_scale {
317        let (f, f_infallible) =
318            make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)?;
319        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
320    } else {
321        let Some((f, f_infallible)) =
322            make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
323        else {
324            // Scale reduction exceeds supported precision; result mathematically rounds to zero
325            return Some(O::Native::ZERO);
326        };
327        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
328    }
329}
330
331fn cast_decimal_to_decimal_error<I, O>(
332    output_precision: u8,
333    output_scale: i8,
334) -> impl Fn(<I as ArrowPrimitiveType>::Native) -> ArrowError
335where
336    I: DecimalType,
337    O: DecimalType,
338    I::Native: DecimalCast + ArrowNativeTypeOp,
339    O::Native: DecimalCast + ArrowNativeTypeOp,
340{
341    move |x: I::Native| {
342        ArrowError::CastError(format!(
343            "Cannot cast to {}({}, {}). Overflowing on {:?}",
344            O::PREFIX,
345            output_precision,
346            output_scale,
347            x
348        ))
349    }
350}
351
352fn apply_decimal_cast<I: DecimalType, O: DecimalType>(
353    array: &PrimitiveArray<I>,
354    output_precision: u8,
355    output_scale: i8,
356    f_fallible: impl Fn(I::Native) -> Option<O::Native>,
357    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
358    cast_options: &CastOptions,
359) -> Result<PrimitiveArray<O>, ArrowError>
360where
361    I::Native: DecimalCast + ArrowNativeTypeOp,
362    O::Native: DecimalCast + ArrowNativeTypeOp,
363{
364    let array = if let Some(f_infallible) = f_infallible {
365        array.unary(f_infallible)
366    } else if cast_options.safe {
367        array.unary_opt(|x| {
368            f_fallible(x).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
369        })
370    } else {
371        let error = cast_decimal_to_decimal_error::<I, O>(output_precision, output_scale);
372        array.try_unary(|x| {
373            let v = f_fallible(x).ok_or_else(|| error(x))?;
374            O::validate_decimal_precision(v, output_precision, output_scale).map(|()| v)
375        })?
376    };
377    Ok(array)
378}
379
380fn convert_to_smaller_scale_decimal<I, O>(
381    array: &PrimitiveArray<I>,
382    input_precision: u8,
383    input_scale: i8,
384    output_precision: u8,
385    output_scale: i8,
386    cast_options: &CastOptions,
387) -> Result<PrimitiveArray<O>, ArrowError>
388where
389    I: DecimalType,
390    O: DecimalType,
391    I::Native: DecimalCast + ArrowNativeTypeOp,
392    O::Native: DecimalCast + ArrowNativeTypeOp,
393{
394    if let Some((f_fallible, f_infallible)) =
395        make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
396    {
397        apply_decimal_cast(
398            array,
399            output_precision,
400            output_scale,
401            f_fallible,
402            f_infallible,
403            cast_options,
404        )
405    } else {
406        // Scale reduction exceeds supported precision; result mathematically rounds to zero
407        let zeros = vec![O::Native::ZERO; array.len()];
408        Ok(PrimitiveArray::new(zeros.into(), array.nulls().cloned()))
409    }
410}
411
412fn convert_to_bigger_or_equal_scale_decimal<I, O>(
413    array: &PrimitiveArray<I>,
414    input_precision: u8,
415    input_scale: i8,
416    output_precision: u8,
417    output_scale: i8,
418    cast_options: &CastOptions,
419) -> Result<PrimitiveArray<O>, ArrowError>
420where
421    I: DecimalType,
422    O: DecimalType,
423    I::Native: DecimalCast + ArrowNativeTypeOp,
424    O::Native: DecimalCast + ArrowNativeTypeOp,
425{
426    if let Some((f, f_infallible)) =
427        make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
428    {
429        apply_decimal_cast(
430            array,
431            output_precision,
432            output_scale,
433            f,
434            f_infallible,
435            cast_options,
436        )
437    } else {
438        // Scale increase exceeds supported precision; return overflow error
439        Err(ArrowError::CastError(format!(
440            "Cannot cast to {}({}, {}). Value overflows for output scale",
441            O::PREFIX,
442            output_precision,
443            output_scale
444        )))
445    }
446}
447
448// Only support one type of decimal cast operations
449pub(crate) fn cast_decimal_to_decimal_same_type<T>(
450    array: &PrimitiveArray<T>,
451    input_precision: u8,
452    input_scale: i8,
453    output_precision: u8,
454    output_scale: i8,
455    cast_options: &CastOptions,
456) -> Result<ArrayRef, ArrowError>
457where
458    T: DecimalType,
459    T::Native: DecimalCast + ArrowNativeTypeOp,
460{
461    let array: PrimitiveArray<T> =
462        if input_scale == output_scale && input_precision <= output_precision {
463            array.clone()
464        } else if input_scale <= output_scale {
465            convert_to_bigger_or_equal_scale_decimal::<T, T>(
466                array,
467                input_precision,
468                input_scale,
469                output_precision,
470                output_scale,
471                cast_options,
472            )?
473        } else {
474            // input_scale > output_scale
475            convert_to_smaller_scale_decimal::<T, T>(
476                array,
477                input_precision,
478                input_scale,
479                output_precision,
480                output_scale,
481                cast_options,
482            )?
483        };
484
485    Ok(Arc::new(array.with_precision_and_scale(
486        output_precision,
487        output_scale,
488    )?))
489}
490
491// Support two different types of decimal cast operations
492pub(crate) fn cast_decimal_to_decimal<I, O>(
493    array: &PrimitiveArray<I>,
494    input_precision: u8,
495    input_scale: i8,
496    output_precision: u8,
497    output_scale: i8,
498    cast_options: &CastOptions,
499) -> Result<ArrayRef, ArrowError>
500where
501    I: DecimalType,
502    O: DecimalType,
503    I::Native: DecimalCast + ArrowNativeTypeOp,
504    O::Native: DecimalCast + ArrowNativeTypeOp,
505{
506    let array: PrimitiveArray<O> = if input_scale > output_scale {
507        convert_to_smaller_scale_decimal::<I, O>(
508            array,
509            input_precision,
510            input_scale,
511            output_precision,
512            output_scale,
513            cast_options,
514        )?
515    } else {
516        convert_to_bigger_or_equal_scale_decimal::<I, O>(
517            array,
518            input_precision,
519            input_scale,
520            output_precision,
521            output_scale,
522            cast_options,
523        )?
524    };
525
526    Ok(Arc::new(array.with_precision_and_scale(
527        output_precision,
528        output_scale,
529    )?))
530}
531
532/// Parses the given string as a decimal with the given scale, returning the
533/// unscaled representation in the decimal type's native integer (e.g. `i32`
534/// for `Decimal32Type`, `i256` for `Decimal256Type`).
535///
536/// Returns an error if the input is not a valid decimal string, or if the
537/// scaled and rounded value does not fit the maximum precision of the decimal
538/// type. The caller is responsible for validating the result against any
539/// smaller target precision.
540#[deprecated(
541    since = "60.0.0",
542    note = "Use `arrow_cast::parse::parse_decimal` instead"
543)]
544pub fn parse_string_to_decimal_native<T: DecimalType>(
545    value_str: &str,
546    scale: usize,
547) -> Result<T::Native, ArrowError> {
548    let overflow = || {
549        ArrowError::InvalidArgumentError(format!(
550            "Cannot convert {value_str} to {}: Overflow",
551            T::PREFIX
552        ))
553    };
554    let scale = i8::try_from(scale).map_err(|_| overflow())?;
555    parse_decimal_checked::<T>(value_str, T::MAX_PRECISION, scale).map_err(|e| match e {
556        DecimalParseError::InvalidFormat => {
557            ArrowError::InvalidArgumentError(format!("Invalid decimal format: {value_str:?}"))
558        }
559        DecimalParseError::Overflow => overflow(),
560    })
561}
562
563pub(crate) fn generic_string_to_decimal_cast<'a, T, S>(
564    from: &'a S,
565    precision: u8,
566    scale: i8,
567    cast_options: &CastOptions,
568) -> Result<PrimitiveArray<T>, ArrowError>
569where
570    T: DecimalType,
571    &'a S: StringArrayType<'a>,
572{
573    if cast_options.safe {
574        let iter = from
575            .iter()
576            .map(|v| parse_decimal_checked::<T>(v?, precision, scale).ok());
577        // Benefit:
578        //     15-19% faster than appending to a PrimitiveBuilder (measured
579        //     with the cast_kernels string-to-decimal benchmarks)
580        // Soundness:
581        //     The iterator is trustedLen because it comes from a `StringArray`.
582        Ok(unsafe {
583            PrimitiveArray::<T>::from_trusted_len_iter(iter)
584                .with_precision_and_scale(precision, scale)?
585        })
586    } else {
587        let mut builder = PrimitiveBuilder::<T>::with_capacity(from.len());
588        for v in from.iter() {
589            match v {
590                Some(v) => {
591                    let v = parse_decimal_checked::<T>(v, precision, scale).map_err(|e| {
592                        let reason = match e {
593                            DecimalParseError::InvalidFormat => "invalid decimal format",
594                            DecimalParseError::Overflow => "value does not fit",
595                        };
596                        ArrowError::CastError(format!(
597                            "Cannot cast string '{v}' to value of {}({precision}, {scale}) type: {reason}",
598                            T::PREFIX,
599                        ))
600                    })?;
601                    builder.append_value(v);
602                }
603                None => builder.append_null(),
604            }
605        }
606        builder.finish().with_precision_and_scale(precision, scale)
607    }
608}
609
610pub(crate) fn string_to_decimal_cast<T: DecimalType, Offset: OffsetSizeTrait>(
611    from: &GenericStringArray<Offset>,
612    precision: u8,
613    scale: i8,
614    cast_options: &CastOptions,
615) -> Result<PrimitiveArray<T>, ArrowError> {
616    generic_string_to_decimal_cast::<T, GenericStringArray<Offset>>(
617        from,
618        precision,
619        scale,
620        cast_options,
621    )
622}
623
624pub(crate) fn string_view_to_decimal_cast<T: DecimalType>(
625    from: &StringViewArray,
626    precision: u8,
627    scale: i8,
628    cast_options: &CastOptions,
629) -> Result<PrimitiveArray<T>, ArrowError> {
630    generic_string_to_decimal_cast::<T, StringViewArray>(from, precision, scale, cast_options)
631}
632
633/// Cast Utf8 to decimal
634pub(crate) fn cast_string_to_decimal<T: DecimalType, Offset: OffsetSizeTrait>(
635    from: &dyn Array,
636    precision: u8,
637    scale: i8,
638    cast_options: &CastOptions,
639) -> Result<ArrayRef, ArrowError> {
640    validate_decimal_precision_and_scale::<T>(precision, scale)?;
641
642    let result = match from.data_type() {
643        DataType::Utf8View => string_view_to_decimal_cast::<T>(
644            from.as_any().downcast_ref::<StringViewArray>().unwrap(),
645            precision,
646            scale,
647            cast_options,
648        )?,
649        DataType::Utf8 | DataType::LargeUtf8 => string_to_decimal_cast::<T, Offset>(
650            from.as_any()
651                .downcast_ref::<GenericStringArray<Offset>>()
652                .unwrap(),
653            precision,
654            scale,
655            cast_options,
656        )?,
657        other => {
658            return Err(ArrowError::ComputeError(format!(
659                "Cannot cast {other:?} to decimal",
660            )));
661        }
662    };
663
664    Ok(Arc::new(result))
665}
666
667pub(crate) fn cast_floating_point_to_decimal<T: ArrowPrimitiveType, D>(
668    array: &PrimitiveArray<T>,
669    precision: u8,
670    scale: i8,
671    cast_options: &CastOptions,
672) -> Result<ArrayRef, ArrowError>
673where
674    <T as ArrowPrimitiveType>::Native: AsPrimitive<f64>,
675    D: DecimalType + ArrowPrimitiveType,
676    <D as ArrowPrimitiveType>::Native: DecimalCast,
677{
678    let mul = 10_f64.powi(scale as i32);
679
680    if cast_options.safe {
681        array
682            .unary_opt::<_, D>(|v| {
683                single_float_to_decimal::<D>(v.as_(), mul)
684                    .filter(|v| D::is_valid_decimal_precision(*v, precision))
685            })
686            .with_precision_and_scale(precision, scale)
687            .map(|a| Arc::new(a) as ArrayRef)
688    } else {
689        array
690            .try_unary::<_, D, _>(|v| {
691                let v = single_float_to_decimal::<D>(v.as_(), mul).ok_or_else(|| {
692                    ArrowError::CastError(format!(
693                        "Cannot cast to {}({}, {}). Overflowing on {:?}",
694                        D::PREFIX,
695                        precision,
696                        scale,
697                        v
698                    ))
699                })?;
700                D::validate_decimal_precision(v, precision, scale).map(|()| v)
701            })?
702            .with_precision_and_scale(precision, scale)
703            .map(|a| Arc::new(a) as ArrayRef)
704    }
705}
706
707/// Cast a single floating point value to a decimal native with the given multiple.
708/// Returns `None` if the value cannot be represented with the requested precision.
709#[inline(always)]
710pub fn single_float_to_decimal<D>(input: f64, mul: f64) -> Option<D::Native>
711where
712    D: DecimalType + ArrowPrimitiveType,
713    <D as ArrowPrimitiveType>::Native: DecimalCast,
714{
715    D::Native::from_f64((mul * input).round())
716}
717
718pub(crate) fn cast_decimal_to_integer<D, T>(
719    array: &dyn Array,
720    base: D::Native,
721    scale: i8,
722    cast_options: &CastOptions,
723) -> Result<ArrayRef, ArrowError>
724where
725    T: ArrowPrimitiveType,
726    <T as ArrowPrimitiveType>::Native: NumCast,
727    D: DecimalType + ArrowPrimitiveType,
728    <D as ArrowPrimitiveType>::Native: ToPrimitive,
729{
730    let array = array.as_primitive::<D>();
731
732    let div: D::Native = base.pow_checked(scale.unsigned_abs() as u32).map_err(|_| {
733        ArrowError::CastError(format!(
734            "Cannot cast to {:?}. The scale {} causes overflow.",
735            D::PREFIX,
736            scale,
737        ))
738    })?;
739
740    let mut value_builder = PrimitiveBuilder::<T>::with_capacity(array.len());
741
742    if scale < 0 {
743        match cast_options.safe {
744            true => {
745                for i in 0..array.len() {
746                    if array.is_null(i) {
747                        value_builder.append_null();
748                    } else {
749                        let v = array
750                            .value(i)
751                            .mul_checked(div)
752                            .ok()
753                            .and_then(<T::Native as NumCast>::from::<D::Native>);
754                        value_builder.append_option(v);
755                    }
756                }
757            }
758            false => {
759                for i in 0..array.len() {
760                    if array.is_null(i) {
761                        value_builder.append_null();
762                    } else {
763                        let v = array.value(i).mul_checked(div)?;
764
765                        let value =
766                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
767                                ArrowError::CastError(format!(
768                                    "value of {:?} is out of range {}",
769                                    v,
770                                    T::DATA_TYPE
771                                ))
772                            })?;
773
774                        value_builder.append_value(value);
775                    }
776                }
777            }
778        }
779    } else {
780        match cast_options.safe {
781            true => {
782                for i in 0..array.len() {
783                    if array.is_null(i) {
784                        value_builder.append_null();
785                    } else {
786                        let v = array
787                            .value(i)
788                            .div_checked(div)
789                            .ok()
790                            .and_then(<T::Native as NumCast>::from::<D::Native>);
791                        value_builder.append_option(v);
792                    }
793                }
794            }
795            false => {
796                for i in 0..array.len() {
797                    if array.is_null(i) {
798                        value_builder.append_null();
799                    } else {
800                        let v = array.value(i).div_checked(div)?;
801
802                        let value =
803                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
804                                ArrowError::CastError(format!(
805                                    "value of {:?} is out of range {}",
806                                    v,
807                                    T::DATA_TYPE
808                                ))
809                            })?;
810
811                        value_builder.append_value(value);
812                    }
813                }
814            }
815        }
816    }
817    Ok(Arc::new(value_builder.finish()))
818}
819
820/// Cast a decimal array to a floating point array.
821///
822/// Conversion is lossy and follows standard floating point semantics. Values
823/// that exceed the representable range become `INFINITY` or `-INFINITY` without
824/// returning an error.
825pub(crate) fn cast_decimal_to_float<D: DecimalType, T: ArrowPrimitiveType, F>(
826    array: &dyn Array,
827    op: F,
828) -> Result<ArrayRef, ArrowError>
829where
830    F: Fn(D::Native) -> T::Native,
831{
832    let array = array.as_primitive::<D>();
833    let array = array.unary::<_, T>(op);
834    Ok(Arc::new(array))
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840
841    #[test]
842    #[expect(deprecated)]
843    fn test_parse_string_to_decimal_native() {
844        assert_eq!(
845            parse_string_to_decimal_native::<Decimal128Type>("123.456", 2).unwrap(),
846            12346
847        );
848        // The value is checked against the maximum precision of the type, not
849        // against any target precision
850        assert_eq!(
851            parse_string_to_decimal_native::<Decimal128Type>(&"9".repeat(38), 0).unwrap(),
852            10_i128.pow(38) - 1
853        );
854        assert!(
855            parse_string_to_decimal_native::<Decimal128Type>(&i128::MAX.to_string(), 0).is_err()
856        );
857        assert_eq!(
858            parse_string_to_decimal_native::<Decimal128Type>("abc", 2)
859                .unwrap_err()
860                .to_string(),
861            "Invalid argument error: Invalid decimal format: \"abc\""
862        );
863        assert_eq!(
864            parse_string_to_decimal_native::<Decimal32Type>("1", 10)
865                .unwrap_err()
866                .to_string(),
867            "Invalid argument error: Cannot convert 1 to Decimal32: Overflow"
868        );
869    }
870
871    #[test]
872    fn test_rescale_decimal_upscale_within_precision() {
873        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(
874            12_345_i128, // 123.45 with scale 2
875            5,
876            2,
877            8,
878            5,
879        );
880        assert_eq!(result, Some(12_345_000_i128));
881    }
882
883    #[test]
884    fn test_rescale_decimal_downscale_rounds_half_away_from_zero() {
885        let positive = rescale_decimal::<Decimal128Type, Decimal128Type>(
886            1_050_i128, // 1.050 with scale 3
887            5, 3, 5, 1,
888        );
889        assert_eq!(positive, Some(11_i128)); // 1.1 with scale 1
890
891        let negative = rescale_decimal::<Decimal128Type, Decimal128Type>(
892            -1_050_i128, // -1.050 with scale 3
893            5,
894            3,
895            5,
896            1,
897        );
898        assert_eq!(negative, Some(-11_i128)); // -1.1 with scale 1
899    }
900
901    #[test]
902    fn test_rescale_decimal_downscale_large_delta_returns_zero() {
903        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(12_345_i32, 9, 9, 9, 4);
904        assert_eq!(result, Some(0_i32));
905    }
906
907    #[test]
908    fn test_rescale_decimal_upscale_overflow_returns_none() {
909        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(9_999_i32, 4, 0, 5, 2);
910        assert_eq!(result, None);
911    }
912
913    #[test]
914    fn test_rescale_decimal_invalid_input_precision_scale_returns_none() {
915        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 39, 39, 38, 38);
916        assert_eq!(result, None);
917    }
918
919    #[test]
920    fn test_rescale_decimal_invalid_output_precision_scale_returns_none() {
921        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 38, 38, 39, 39);
922        assert_eq!(result, None);
923    }
924}