polars_core/chunked_array/logical/
decimal.rs

1use std::borrow::Cow;
2
3use super::*;
4use crate::chunked_array::cast::cast_chunks;
5use crate::prelude::*;
6
7pub type DecimalChunked = Logical<DecimalType, Int128Type>;
8
9impl Int128Chunked {
10    #[inline]
11    pub fn into_decimal_unchecked(self, precision: Option<usize>, scale: usize) -> DecimalChunked {
12        DecimalChunked::new_logical(self, DataType::Decimal(precision, Some(scale)))
13    }
14
15    pub fn into_decimal(
16        self,
17        precision: Option<usize>,
18        scale: usize,
19    ) -> PolarsResult<DecimalChunked> {
20        // TODO: if precision is None, do we check that the value fits within precision of 38?...
21        if let Some(precision) = precision {
22            let precision_max = 10_i128.pow(precision as u32);
23            if let Some((min, max)) = self.min_max() {
24                let max_abs = max.abs().max(min.abs());
25                polars_ensure!(
26                    max_abs < precision_max,
27                    ComputeError: "decimal precision {} can't fit values with {} digits",
28                    precision,
29                    max_abs.to_string().len()
30                );
31            }
32        }
33        Ok(self.into_decimal_unchecked(precision, scale))
34    }
35}
36
37impl LogicalType for DecimalChunked {
38    fn dtype(&self) -> &DataType {
39        &self.dtype
40    }
41
42    #[inline]
43    fn get_any_value(&self, i: usize) -> PolarsResult<AnyValue<'_>> {
44        polars_ensure!(i < self.len(), oob = i, self.len());
45        Ok(unsafe { self.get_any_value_unchecked(i) })
46    }
47
48    #[inline]
49    unsafe fn get_any_value_unchecked(&self, i: usize) -> AnyValue<'_> {
50        match self.phys.get_unchecked(i) {
51            Some(v) => AnyValue::Decimal(v, self.scale()),
52            None => AnyValue::Null,
53        }
54    }
55
56    fn cast_with_options(
57        &self,
58        dtype: &DataType,
59        cast_options: CastOptions,
60    ) -> PolarsResult<Series> {
61        let mut dtype = Cow::Borrowed(dtype);
62        if let DataType::Decimal(to_precision, to_scale) = dtype.as_ref() {
63            let from_precision = self.precision();
64            let from_scale = self.scale();
65
66            let to_precision = to_precision.or(from_precision);
67            let to_scale = to_scale.unwrap_or(from_scale);
68
69            if to_precision == from_precision && to_scale == from_scale {
70                return Ok(self.clone().into_series());
71            }
72
73            dtype = Cow::Owned(DataType::Decimal(to_precision, Some(to_scale)));
74        }
75
76        let arrow_dtype = self.dtype().to_arrow(CompatLevel::newest());
77        let chunks = self
78            .chunks
79            .iter()
80            .map(|arr| {
81                arr.as_any()
82                    .downcast_ref::<PrimitiveArray<i128>>()
83                    .unwrap()
84                    .clone()
85                    .to(arrow_dtype.clone())
86                    .to_boxed()
87            })
88            .collect::<Vec<_>>();
89        let chunks = cast_chunks(&chunks, dtype.as_ref(), cast_options)?;
90        Series::try_from((self.name().clone(), chunks))
91    }
92}
93
94impl DecimalChunked {
95    pub fn precision(&self) -> Option<usize> {
96        match &self.dtype {
97            DataType::Decimal(precision, _) => *precision,
98            _ => unreachable!(),
99        }
100    }
101
102    pub fn scale(&self) -> usize {
103        match &self.dtype {
104            DataType::Decimal(_, scale) => scale.unwrap_or_else(|| unreachable!()),
105            _ => unreachable!(),
106        }
107    }
108
109    pub fn to_scale(&self, scale: usize) -> PolarsResult<Cow<'_, Self>> {
110        if self.scale() == scale {
111            return Ok(Cow::Borrowed(self));
112        }
113
114        let mut precision = self.precision();
115        if let Some(ref mut precision) = precision {
116            if self.scale() < scale {
117                *precision += scale;
118                *precision = (*precision).min(38);
119            }
120        }
121
122        let s = self.cast_with_options(
123            &DataType::Decimal(precision, Some(scale)),
124            CastOptions::NonStrict,
125        )?;
126        Ok(Cow::Owned(s.decimal().unwrap().clone()))
127    }
128}