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 as i16 - input_scale as i16;
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 i16) + delta_scale <= (output_precision as i16);
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 as i16 - output_scale as i16;
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, r) = x.div_rem_wrapping(div);
241
242        // Round result
243        let adjusted = match x >= I::Native::ZERO {
244            true if r >= half => d.add_wrapping(I::Native::ONE),
245            false if r <= half_neg => d.sub_wrapping(I::Native::ONE),
246            _ => d,
247        };
248        O::Native::from_decimal(adjusted)
249    };
250
251    // if the reduction of the input number through scaling (dividing) is greater
252    // than a possible precision loss (plus potential increase via rounding)
253    // every input number will fit into the output type
254    // Example: If we are starting with any number of precision 5 [xxxxx],
255    // then and decrease the scale by 3 will have the following effect on the representation:
256    // [xxxxx] -> [xx] (+ 1 possibly, due to rounding).
257    // The rounding may add a digit, so the cast to be infallible,
258    // the output type needs to have at least 3 digits of precision.
259    // e.g. Decimal(5, 3) 99.999 to Decimal(3, 0) will result in 100:
260    // [99999] -> [99] + 1 = [100], a cast to Decimal(2, 0) would not be possible
261    let is_infallible_cast = (input_precision as i16) - delta_scale < (output_precision as i16);
262    let f_infallible = is_infallible_cast.then_some(move |x| f_fallible(x).unwrap());
263    Some((f_fallible, f_infallible))
264}
265
266/// Apply the rescaler function to the value.
267/// If the rescaler is infallible, use the infallible function.
268/// Otherwise, use the fallible function and validate the precision.
269fn apply_rescaler<I: DecimalType, O: DecimalType>(
270    value: I::Native,
271    output_precision: u8,
272    f: impl Fn(I::Native) -> Option<O::Native>,
273    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
274) -> Option<O::Native>
275where
276    I::Native: DecimalCast,
277    O::Native: DecimalCast,
278{
279    if let Some(f_infallible) = f_infallible {
280        Some(f_infallible(value))
281    } else {
282        f(value).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
283    }
284}
285
286/// Rescales a decimal value from `(input_precision, input_scale)` to
287/// `(output_precision, output_scale)` and returns the converted number when it fits
288/// within the output precision.
289///
290/// The function first validates that the requested precision and scale are supported for
291/// both the source and destination decimal types. It then either upscales (multiplying
292/// by an appropriate power of ten) or downscales (dividing with rounding) the input value.
293/// When the scaling factor exceeds the precision table of the destination type, the value
294/// is treated as an overflow for upscaling, or rounded to zero for downscaling (as any
295/// possible result would be zero at the requested scale).
296///
297/// This mirrors the column-oriented helpers of decimal casting but operates on a single value
298/// (row-level) instead of an entire array.
299///
300/// Returns `None` if the value cannot be represented with the requested precision.
301pub fn rescale_decimal<I: DecimalType, O: DecimalType>(
302    value: I::Native,
303    input_precision: u8,
304    input_scale: i8,
305    output_precision: u8,
306    output_scale: i8,
307) -> Option<O::Native>
308where
309    I::Native: DecimalCast + ArrowNativeTypeOp,
310    O::Native: DecimalCast + ArrowNativeTypeOp,
311{
312    validate_decimal_precision_and_scale::<I>(input_precision, input_scale).ok()?;
313    validate_decimal_precision_and_scale::<O>(output_precision, output_scale).ok()?;
314
315    if input_scale <= output_scale {
316        let (f, f_infallible) =
317            make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)?;
318        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
319    } else {
320        let Some((f, f_infallible)) =
321            make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
322        else {
323            // Scale reduction exceeds supported precision; result mathematically rounds to zero
324            return Some(O::Native::ZERO);
325        };
326        apply_rescaler::<I, O>(value, output_precision, f, f_infallible)
327    }
328}
329
330fn cast_decimal_to_decimal_error<I, O>(
331    output_precision: u8,
332    output_scale: i8,
333) -> impl Fn(<I as ArrowPrimitiveType>::Native) -> ArrowError
334where
335    I: DecimalType,
336    O: DecimalType,
337    I::Native: DecimalCast + ArrowNativeTypeOp,
338    O::Native: DecimalCast + ArrowNativeTypeOp,
339{
340    move |x: I::Native| {
341        ArrowError::CastError(format!(
342            "Cannot cast to {}({}, {}). Overflowing on {:?}",
343            O::PREFIX,
344            output_precision,
345            output_scale,
346            x
347        ))
348    }
349}
350
351fn apply_decimal_cast<I: DecimalType, O: DecimalType>(
352    array: &PrimitiveArray<I>,
353    output_precision: u8,
354    output_scale: i8,
355    f_fallible: impl Fn(I::Native) -> Option<O::Native>,
356    f_infallible: Option<impl Fn(I::Native) -> O::Native>,
357    cast_options: &CastOptions,
358) -> Result<PrimitiveArray<O>, ArrowError>
359where
360    I::Native: DecimalCast + ArrowNativeTypeOp,
361    O::Native: DecimalCast + ArrowNativeTypeOp,
362{
363    let array = if let Some(f_infallible) = f_infallible {
364        array.unary(f_infallible)
365    } else if cast_options.safe {
366        array.unary_opt(|x| {
367            f_fallible(x).filter(|v| O::is_valid_decimal_precision(*v, output_precision))
368        })
369    } else {
370        let error = cast_decimal_to_decimal_error::<I, O>(output_precision, output_scale);
371        array.try_unary(|x| {
372            let v = f_fallible(x).ok_or_else(|| error(x))?;
373            O::validate_decimal_precision(v, output_precision, output_scale).map(|()| v)
374        })?
375    };
376    Ok(array)
377}
378
379fn convert_to_smaller_scale_decimal<I, O>(
380    array: &PrimitiveArray<I>,
381    input_precision: u8,
382    input_scale: i8,
383    output_precision: u8,
384    output_scale: i8,
385    cast_options: &CastOptions,
386) -> Result<PrimitiveArray<O>, ArrowError>
387where
388    I: DecimalType,
389    O: DecimalType,
390    I::Native: DecimalCast + ArrowNativeTypeOp,
391    O::Native: DecimalCast + ArrowNativeTypeOp,
392{
393    if let Some((f_fallible, f_infallible)) =
394        make_downscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
395    {
396        apply_decimal_cast(
397            array,
398            output_precision,
399            output_scale,
400            f_fallible,
401            f_infallible,
402            cast_options,
403        )
404    } else {
405        // Scale reduction exceeds supported precision; result mathematically rounds to zero
406        let zeros = vec![O::Native::ZERO; array.len()];
407        Ok(PrimitiveArray::new(zeros.into(), array.nulls().cloned()))
408    }
409}
410
411fn convert_to_bigger_or_equal_scale_decimal<I, O>(
412    array: &PrimitiveArray<I>,
413    input_precision: u8,
414    input_scale: i8,
415    output_precision: u8,
416    output_scale: i8,
417    cast_options: &CastOptions,
418) -> Result<PrimitiveArray<O>, ArrowError>
419where
420    I: DecimalType,
421    O: DecimalType,
422    I::Native: DecimalCast + ArrowNativeTypeOp,
423    O::Native: DecimalCast + ArrowNativeTypeOp,
424{
425    if let Some((f, f_infallible)) =
426        make_upscaler::<I, O>(input_precision, input_scale, output_precision, output_scale)
427    {
428        apply_decimal_cast(
429            array,
430            output_precision,
431            output_scale,
432            f,
433            f_infallible,
434            cast_options,
435        )
436    } else {
437        // Scale increase exceeds supported precision; return overflow error
438        Err(ArrowError::CastError(format!(
439            "Cannot cast to {}({}, {}). Value overflows for output scale",
440            O::PREFIX,
441            output_precision,
442            output_scale
443        )))
444    }
445}
446
447// Only support one type of decimal cast operations
448pub(crate) fn cast_decimal_to_decimal_same_type<T>(
449    array: &PrimitiveArray<T>,
450    input_precision: u8,
451    input_scale: i8,
452    output_precision: u8,
453    output_scale: i8,
454    cast_options: &CastOptions,
455) -> Result<ArrayRef, ArrowError>
456where
457    T: DecimalType,
458    T::Native: DecimalCast + ArrowNativeTypeOp,
459{
460    let array: PrimitiveArray<T> =
461        if input_scale == output_scale && input_precision <= output_precision {
462            array.clone()
463        } else if input_scale <= output_scale {
464            convert_to_bigger_or_equal_scale_decimal::<T, T>(
465                array,
466                input_precision,
467                input_scale,
468                output_precision,
469                output_scale,
470                cast_options,
471            )?
472        } else {
473            // input_scale > output_scale
474            convert_to_smaller_scale_decimal::<T, T>(
475                array,
476                input_precision,
477                input_scale,
478                output_precision,
479                output_scale,
480                cast_options,
481            )?
482        };
483
484    Ok(Arc::new(array.with_precision_and_scale(
485        output_precision,
486        output_scale,
487    )?))
488}
489
490// Support two different types of decimal cast operations
491pub(crate) fn cast_decimal_to_decimal<I, O>(
492    array: &PrimitiveArray<I>,
493    input_precision: u8,
494    input_scale: i8,
495    output_precision: u8,
496    output_scale: i8,
497    cast_options: &CastOptions,
498) -> Result<ArrayRef, ArrowError>
499where
500    I: DecimalType,
501    O: DecimalType,
502    I::Native: DecimalCast + ArrowNativeTypeOp,
503    O::Native: DecimalCast + ArrowNativeTypeOp,
504{
505    let array: PrimitiveArray<O> = if input_scale > output_scale {
506        convert_to_smaller_scale_decimal::<I, O>(
507            array,
508            input_precision,
509            input_scale,
510            output_precision,
511            output_scale,
512            cast_options,
513        )?
514    } else {
515        convert_to_bigger_or_equal_scale_decimal::<I, O>(
516            array,
517            input_precision,
518            input_scale,
519            output_precision,
520            output_scale,
521            cast_options,
522        )?
523    };
524
525    Ok(Arc::new(array.with_precision_and_scale(
526        output_precision,
527        output_scale,
528    )?))
529}
530
531/// Parses the given string as a decimal with the given scale, returning the
532/// unscaled representation in the decimal type's native integer (e.g. `i32`
533/// for `Decimal32Type`, `i256` for `Decimal256Type`).
534///
535/// Returns an error if the input is not a valid decimal string, or if the
536/// scaled and rounded value does not fit the maximum precision of the decimal
537/// type. The caller is responsible for validating the result against any
538/// smaller target precision.
539#[deprecated(
540    since = "60.0.0",
541    note = "Use `arrow_cast::parse::parse_decimal` instead"
542)]
543pub fn parse_string_to_decimal_native<T: DecimalType>(
544    value_str: &str,
545    scale: usize,
546) -> Result<T::Native, ArrowError> {
547    let overflow = || {
548        ArrowError::InvalidArgumentError(format!(
549            "Cannot convert {value_str} to {}: Overflow",
550            T::PREFIX
551        ))
552    };
553    let scale = i8::try_from(scale).map_err(|_| overflow())?;
554    parse_decimal_checked::<T>(value_str, T::MAX_PRECISION, scale).map_err(|e| match e {
555        DecimalParseError::InvalidFormat => {
556            ArrowError::InvalidArgumentError(format!("Invalid decimal format: {value_str:?}"))
557        }
558        DecimalParseError::Overflow => overflow(),
559    })
560}
561
562pub(crate) fn generic_string_to_decimal_cast<'a, T, S>(
563    from: &'a S,
564    precision: u8,
565    scale: i8,
566    cast_options: &CastOptions,
567) -> Result<PrimitiveArray<T>, ArrowError>
568where
569    T: DecimalType,
570    &'a S: StringArrayType<'a>,
571{
572    if cast_options.safe {
573        let iter = from
574            .iter()
575            .map(|v| parse_decimal_checked::<T>(v?, precision, scale).ok());
576        // Benefit:
577        //     15-19% faster than appending to a PrimitiveBuilder (measured
578        //     with the cast_kernels string-to-decimal benchmarks)
579        // Soundness:
580        //     The iterator is trustedLen because it comes from a `StringArray`.
581        Ok(unsafe {
582            PrimitiveArray::<T>::from_trusted_len_iter(iter)
583                .with_precision_and_scale(precision, scale)?
584        })
585    } else {
586        let mut builder = PrimitiveBuilder::<T>::with_capacity(from.len());
587        for v in from.iter() {
588            match v {
589                Some(v) => {
590                    let v = parse_decimal_checked::<T>(v, precision, scale).map_err(|e| {
591                        let reason = match e {
592                            DecimalParseError::InvalidFormat => "invalid decimal format",
593                            DecimalParseError::Overflow => "value does not fit",
594                        };
595                        ArrowError::CastError(format!(
596                            "Cannot cast string '{v}' to value of {}({precision}, {scale}) type: {reason}",
597                            T::PREFIX,
598                        ))
599                    })?;
600                    builder.append_value(v);
601                }
602                None => builder.append_null(),
603            }
604        }
605        builder.finish().with_precision_and_scale(precision, scale)
606    }
607}
608
609pub(crate) fn string_to_decimal_cast<T: DecimalType, Offset: OffsetSizeTrait>(
610    from: &GenericStringArray<Offset>,
611    precision: u8,
612    scale: i8,
613    cast_options: &CastOptions,
614) -> Result<PrimitiveArray<T>, ArrowError> {
615    generic_string_to_decimal_cast::<T, GenericStringArray<Offset>>(
616        from,
617        precision,
618        scale,
619        cast_options,
620    )
621}
622
623pub(crate) fn string_view_to_decimal_cast<T: DecimalType>(
624    from: &StringViewArray,
625    precision: u8,
626    scale: i8,
627    cast_options: &CastOptions,
628) -> Result<PrimitiveArray<T>, ArrowError> {
629    generic_string_to_decimal_cast::<T, StringViewArray>(from, precision, scale, cast_options)
630}
631
632/// Cast Utf8 to decimal
633pub(crate) fn cast_string_to_decimal<T: DecimalType, Offset: OffsetSizeTrait>(
634    from: &dyn Array,
635    precision: u8,
636    scale: i8,
637    cast_options: &CastOptions,
638) -> Result<ArrayRef, ArrowError> {
639    validate_decimal_precision_and_scale::<T>(precision, scale)?;
640
641    let result = match from.data_type() {
642        DataType::Utf8View => string_view_to_decimal_cast::<T>(
643            from.as_any().downcast_ref::<StringViewArray>().unwrap(),
644            precision,
645            scale,
646            cast_options,
647        )?,
648        DataType::Utf8 | DataType::LargeUtf8 => string_to_decimal_cast::<T, Offset>(
649            from.as_any()
650                .downcast_ref::<GenericStringArray<Offset>>()
651                .unwrap(),
652            precision,
653            scale,
654            cast_options,
655        )?,
656        other => {
657            return Err(ArrowError::ComputeError(format!(
658                "Cannot cast {other:?} to decimal",
659            )));
660        }
661    };
662
663    Ok(Arc::new(result))
664}
665
666pub(crate) fn cast_floating_point_to_decimal<T: ArrowPrimitiveType, D>(
667    array: &PrimitiveArray<T>,
668    precision: u8,
669    scale: i8,
670    cast_options: &CastOptions,
671) -> Result<ArrayRef, ArrowError>
672where
673    <T as ArrowPrimitiveType>::Native: AsPrimitive<f64>,
674    D: DecimalType + ArrowPrimitiveType,
675    <D as ArrowPrimitiveType>::Native: DecimalCast,
676{
677    validate_decimal_precision_and_scale::<D>(precision, scale)?;
678    let mul = 10_f64.powi(scale as i32);
679
680    if cast_options.safe {
681        array
682            .unary_opt::<_, D>(|v| float_to_decimal_checked::<D>(v.as_(), mul, precision).ok())
683            .with_precision_and_scale(precision, scale)
684            .map(|a| Arc::new(a) as ArrayRef)
685    } else {
686        array
687            .try_unary::<_, D, _>(|v| {
688                float_to_decimal_checked::<D>(v.as_(), mul, precision)
689                    .map_err(|e| e.into_arrow_error(v, precision, scale))
690            })?
691            .with_precision_and_scale(precision, scale)
692            .map(|a| Arc::new(a) as ArrayRef)
693    }
694}
695
696/// Converts a floating point value to the unscaled integer representation of a decimal.
697///
698/// Scales the input by `10^scale` and rounds half away from zero. Returns an error
699/// if the precision or scale is invalid, or the result does not fit the native
700/// type or the requested precision. Non-finite inputs are rejected.
701/// Use [`float_to_decimal_opt`] when the reason for failure is not needed.
702///
703/// ```
704/// use arrow_array::types::Decimal32Type;
705/// use arrow_cast::cast::float_to_decimal;
706///
707/// assert_eq!(float_to_decimal::<Decimal32Type>(12.345, 5, 2).unwrap(), 1235);
708/// assert!(float_to_decimal::<Decimal32Type>(12345.678, 5, 2).is_err());
709/// ```
710#[inline]
711pub fn float_to_decimal<D>(input: f64, precision: u8, scale: i8) -> Result<D::Native, ArrowError>
712where
713    D: DecimalType,
714    D::Native: DecimalCast,
715{
716    validate_decimal_precision_and_scale::<D>(precision, scale)?;
717    float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision)
718        .map_err(|e| e.into_arrow_error(input, precision, scale))
719}
720
721/// Like [`float_to_decimal`], but returns `None` on failure.
722/// With valid precision and scale, conversion failures do not allocate an error message.
723///
724/// ```
725/// use arrow_array::types::Decimal32Type;
726/// use arrow_cast::cast::float_to_decimal_opt;
727///
728/// assert_eq!(float_to_decimal_opt::<Decimal32Type>(12.345, 5, 2), Some(1235));
729/// assert_eq!(float_to_decimal_opt::<Decimal32Type>(12345.678, 5, 2), None);
730/// ```
731pub fn float_to_decimal_opt<D>(input: f64, precision: u8, scale: i8) -> Option<D::Native>
732where
733    D: DecimalType,
734    D::Native: DecimalCast,
735{
736    validate_decimal_precision_and_scale::<D>(precision, scale).ok()?;
737    float_to_decimal_checked::<D>(input, 10_f64.powi(scale as i32), precision).ok()
738}
739
740// Keep failure reporting allocation-free for scalar Option and safe array casts.
741enum FloatToDecimalError<D: DecimalType> {
742    NativeOverflow,
743    PrecisionOverflow(D::Native),
744}
745
746impl<D: DecimalType> FloatToDecimalError<D> {
747    fn into_arrow_error(self, input: impl std::fmt::Debug, precision: u8, scale: i8) -> ArrowError {
748        match self {
749            Self::NativeOverflow => ArrowError::CastError(format!(
750                "Cannot cast to {}({}, {}). Overflowing on {:?}",
751                D::PREFIX,
752                precision,
753                scale,
754                input
755            )),
756            Self::PrecisionOverflow(value) => {
757                D::validate_decimal_precision(value, precision, scale).unwrap_err()
758            }
759        }
760    }
761}
762
763// `mul` is precomputed once for array casts. Precision must already be validated.
764#[inline]
765fn float_to_decimal_checked<D>(
766    input: f64,
767    mul: f64,
768    precision: u8,
769) -> Result<D::Native, FloatToDecimalError<D>>
770where
771    D: DecimalType,
772    D::Native: DecimalCast,
773{
774    let value =
775        D::Native::from_f64((mul * input).round()).ok_or(FloatToDecimalError::NativeOverflow)?;
776    if D::is_valid_decimal_precision(value, precision) {
777        Ok(value)
778    } else {
779        Err(FloatToDecimalError::PrecisionOverflow(value))
780    }
781}
782
783/// Cast a single floating point value to a decimal native with the given multiple.
784/// Returns `None` if the scaled and rounded value does not fit the native integer
785/// type. The caller must separately validate the decimal precision.
786#[deprecated(since = "60.0.0", note = "Use `float_to_decimal` instead")]
787#[inline(always)]
788pub fn single_float_to_decimal<D>(input: f64, mul: f64) -> Option<D::Native>
789where
790    D: DecimalType + ArrowPrimitiveType,
791    <D as ArrowPrimitiveType>::Native: DecimalCast,
792{
793    D::Native::from_f64((mul * input).round())
794}
795
796pub(crate) fn cast_decimal_to_integer<D, T>(
797    array: &dyn Array,
798    scale: i8,
799    cast_options: &CastOptions,
800) -> Result<ArrayRef, ArrowError>
801where
802    T: ArrowPrimitiveType,
803    <T as ArrowPrimitiveType>::Native: NumCast,
804    D: DecimalType + ArrowPrimitiveType,
805    <D as ArrowPrimitiveType>::Native: ToPrimitive,
806{
807    let array = array.as_primitive::<D>();
808
809    // div is always > 0, so div_wrapping(div) can never overflow/panic
810    let div: D::Native = D::Native::usize_as(10)
811        .pow_checked(scale.unsigned_abs() as u32)
812        .map_err(|_| {
813            ArrowError::CastError(format!(
814                "Cannot cast to {:?}. The scale {} causes overflow.",
815                D::PREFIX,
816                scale,
817            ))
818        })?;
819
820    let mut value_builder = PrimitiveBuilder::<T>::with_capacity(array.len());
821
822    if scale < 0 {
823        match cast_options.safe {
824            true => {
825                for i in 0..array.len() {
826                    if array.is_null(i) {
827                        value_builder.append_null();
828                    } else {
829                        let v = array
830                            .value(i)
831                            .mul_checked(div)
832                            .ok()
833                            .and_then(<T::Native as NumCast>::from::<D::Native>);
834                        value_builder.append_option(v);
835                    }
836                }
837            }
838            false => {
839                for i in 0..array.len() {
840                    if array.is_null(i) {
841                        value_builder.append_null();
842                    } else {
843                        let v = array.value(i).mul_checked(div)?;
844
845                        let value =
846                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
847                                ArrowError::CastError(format!(
848                                    "value of {:?} is out of range {}",
849                                    v,
850                                    T::DATA_TYPE
851                                ))
852                            })?;
853
854                        value_builder.append_value(value);
855                    }
856                }
857            }
858        }
859    } else {
860        match cast_options.safe {
861            true => {
862                for i in 0..array.len() {
863                    if array.is_null(i) {
864                        value_builder.append_null();
865                    } else {
866                        let v = array.value(i).div_wrapping(div);
867                        let v = <T::Native as NumCast>::from(v);
868                        value_builder.append_option(v);
869                    }
870                }
871            }
872            false => {
873                for i in 0..array.len() {
874                    if array.is_null(i) {
875                        value_builder.append_null();
876                    } else {
877                        let v = array.value(i).div_wrapping(div);
878
879                        let value =
880                            <T::Native as NumCast>::from::<D::Native>(v).ok_or_else(|| {
881                                ArrowError::CastError(format!(
882                                    "value of {:?} is out of range {}",
883                                    v,
884                                    T::DATA_TYPE
885                                ))
886                            })?;
887
888                        value_builder.append_value(value);
889                    }
890                }
891            }
892        }
893    }
894    Ok(Arc::new(value_builder.finish()))
895}
896
897/// Cast a decimal array to a floating point array.
898///
899/// Conversion is lossy and follows standard floating point semantics. Values
900/// that exceed the representable range become `INFINITY` or `-INFINITY` without
901/// returning an error.
902pub(crate) fn cast_decimal_to_float<D: DecimalType, T: ArrowPrimitiveType, F>(
903    array: &dyn Array,
904    op: F,
905) -> Result<ArrayRef, ArrowError>
906where
907    F: Fn(D::Native) -> T::Native,
908{
909    let array = array.as_primitive::<D>();
910    let array = array.unary::<_, T>(op);
911    Ok(Arc::new(array))
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917
918    #[test]
919    fn test_float_to_decimal() {
920        fn check<D: DecimalType>()
921        where
922            D::Native: DecimalCast,
923        {
924            for (input, scale, expected) in [
925                (12.345, 2, 1235),
926                (-12.345, 2, -1235),
927                (999.99, 2, 99999),
928                (-999.99, 2, -99999),
929                (99999.25, 0, 99999),
930                (999992.5, -1, 99999),
931                (0.0, 2, 0),
932            ] {
933                assert_eq!(
934                    float_to_decimal_opt::<D>(input, 5, scale),
935                    float_to_decimal::<D>(input, 5, scale).ok(),
936                );
937                assert_eq!(
938                    float_to_decimal::<D>(input, 5, scale)
939                        .unwrap()
940                        .to_i128()
941                        .unwrap(),
942                    expected,
943                );
944            }
945            for (input, scale) in [
946                (12345.678, 2),
947                (-12345.678, 2),
948                (999.9975, 2),
949                (-999.9975, 2),
950                (99999.75, 0),
951                (-99999.75, 0),
952                (999997.5, -1),
953                (-999997.5, -1),
954                (f64::NAN, 0),
955                (f64::INFINITY, 0),
956                (f64::NEG_INFINITY, 0),
957                (f64::MAX, 0),
958            ] {
959                assert!(float_to_decimal::<D>(input, 5, scale).is_err());
960                assert!(float_to_decimal_opt::<D>(input, 5, scale).is_none());
961            }
962            for (precision, scale) in [(0, 0), (D::MAX_PRECISION + 1, 0), (5, 6)] {
963                assert!(float_to_decimal::<D>(0.0, precision, scale).is_err());
964                assert!(float_to_decimal_opt::<D>(0.0, precision, scale).is_none());
965            }
966        }
967        check::<Decimal32Type>();
968        check::<Decimal64Type>();
969        check::<Decimal128Type>();
970        check::<Decimal256Type>();
971    }
972
973    #[test]
974    #[expect(deprecated)]
975    fn test_single_float_to_decimal_preserves_native_range() {
976        // Deprecation preserves acceptance of values that fit i32 but exceed
977        // Decimal32's maximum precision. The new API checks that precision.
978        for (input, expected) in [
979            (1_000_000_000.0, 1_000_000_000),
980            (-1_000_000_000.0, -1_000_000_000),
981            (999_999_999.75, 1_000_000_000),
982            (-999_999_999.75, -1_000_000_000),
983        ] {
984            assert_eq!(
985                single_float_to_decimal::<Decimal32Type>(input, 1.0),
986                Some(expected)
987            );
988            assert!(float_to_decimal::<Decimal32Type>(input, 9, 0).is_err());
989        }
990        assert_eq!(
991            single_float_to_decimal::<Decimal32Type>(999_999_999.0, 1.0),
992            Some(999_999_999),
993        );
994        assert_eq!(
995            single_float_to_decimal::<Decimal32Type>(12.345, 100.0),
996            Some(1235)
997        );
998        assert_eq!(
999            single_float_to_decimal::<Decimal32Type>(f64::MAX, 1.0),
1000            None
1001        );
1002    }
1003
1004    #[test]
1005    #[expect(deprecated)]
1006    fn test_parse_string_to_decimal_native() {
1007        assert_eq!(
1008            parse_string_to_decimal_native::<Decimal128Type>("123.456", 2).unwrap(),
1009            12346
1010        );
1011        // The value is checked against the maximum precision of the type, not
1012        // against any target precision
1013        assert_eq!(
1014            parse_string_to_decimal_native::<Decimal128Type>(&"9".repeat(38), 0).unwrap(),
1015            10_i128.pow(38) - 1
1016        );
1017        assert!(
1018            parse_string_to_decimal_native::<Decimal128Type>(&i128::MAX.to_string(), 0).is_err()
1019        );
1020        assert_eq!(
1021            parse_string_to_decimal_native::<Decimal128Type>("abc", 2)
1022                .unwrap_err()
1023                .to_string(),
1024            "Invalid argument error: Invalid decimal format: \"abc\""
1025        );
1026        assert_eq!(
1027            parse_string_to_decimal_native::<Decimal32Type>("1", 10)
1028                .unwrap_err()
1029                .to_string(),
1030            "Invalid argument error: Cannot convert 1 to Decimal32: Overflow"
1031        );
1032    }
1033
1034    #[test]
1035    fn test_rescale_decimal_upscale_within_precision() {
1036        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(
1037            12_345_i128, // 123.45 with scale 2
1038            5,
1039            2,
1040            8,
1041            5,
1042        );
1043        assert_eq!(result, Some(12_345_000_i128));
1044    }
1045
1046    #[test]
1047    fn test_rescale_decimal_downscale_rounds_half_away_from_zero() {
1048        let positive = rescale_decimal::<Decimal128Type, Decimal128Type>(
1049            1_050_i128, // 1.050 with scale 3
1050            5, 3, 5, 1,
1051        );
1052        assert_eq!(positive, Some(11_i128)); // 1.1 with scale 1
1053
1054        let negative = rescale_decimal::<Decimal128Type, Decimal128Type>(
1055            -1_050_i128, // -1.050 with scale 3
1056            5,
1057            3,
1058            5,
1059            1,
1060        );
1061        assert_eq!(negative, Some(-11_i128)); // -1.1 with scale 1
1062    }
1063
1064    #[test]
1065    fn test_rescale_decimal_downscale_large_delta_returns_zero() {
1066        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(12_345_i32, 9, 9, 9, 4);
1067        assert_eq!(result, Some(0_i32));
1068    }
1069
1070    #[test]
1071    fn test_rescale_decimal_upscale_overflow_returns_none() {
1072        let result = rescale_decimal::<Decimal32Type, Decimal32Type>(9_999_i32, 4, 0, 5, 2);
1073        assert_eq!(result, None);
1074    }
1075
1076    #[test]
1077    fn test_rescale_decimal256_extreme_scales() {
1078        assert_eq!(
1079            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, -76, 76, 0),
1080            None
1081        );
1082        assert_eq!(
1083            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ZERO, 76, -76, 76, 0),
1084            Some(i256::ZERO)
1085        );
1086        assert_eq!(
1087            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, i8::MIN, 76, 76),
1088            None
1089        );
1090        assert_eq!(
1091            rescale_decimal::<Decimal256Type, Decimal256Type>(i256::ONE, 76, 76, 76, i8::MIN),
1092            Some(i256::ZERO)
1093        );
1094    }
1095
1096    #[test]
1097    fn test_rescale_decimal_invalid_input_precision_scale_returns_none() {
1098        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 39, 39, 38, 38);
1099        assert_eq!(result, None);
1100    }
1101
1102    #[test]
1103    fn test_rescale_decimal_invalid_output_precision_scale_returns_none() {
1104        let result = rescale_decimal::<Decimal128Type, Decimal128Type>(123_i128, 38, 38, 39, 39);
1105        assert_eq!(result, None);
1106    }
1107}