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