1use std::cell::Cell;
2
3use polars_arrow::bitmap::Bitmap;
4use polars_arrow::compute::utils::combine_validities_and;
5use polars_compute::arithmetic::DivRounding;
6use polars_compute::decimal::{
7 DEC128_MAX_PREC, dec128_add_scaled, dec128_div_scaled, dec128_int_div_scaled,
8 dec128_mul_scaled, dec128_rem_scaled, dec128_rescale, dec128_sub_scaled, i64_binary_values,
9 i64_unary_values,
10};
11
12use super::*;
13use crate::prelude::arity::{
14 apply_binary_kernel_broadcast, broadcast_binary_elementwise_values,
15 broadcast_try_binary_elementwise,
16};
17
18impl DecimalChunked {
19 fn apply_scaled_kernel(
23 &self,
24 rhs: &Self,
25 scale: usize,
26 op: &str,
27 kernel: impl Fn(i128, usize, i128, usize, usize) -> Option<i128>,
28 ) -> PolarsResult<Self> {
29 let left_s = self.scale();
30 let right_s = rhs.scale();
31 let phys = broadcast_try_binary_elementwise(
32 self.physical(),
33 rhs.physical(),
34 |opt_l, opt_r| {
35 let (Some(l), Some(r)) = (opt_l, opt_r) else {
36 return PolarsResult::Ok(None);
37 };
38 let ret = kernel(l, left_s, r, right_s, scale).ok_or_else(|| {
39 if r == 0 {
41 polars_err!(ComputeError: "division by zero Decimal")
42 } else {
43 polars_err!(
44 ComputeError: "overflow in decimal {op}: result doesn't fit Decimal({DEC128_MAX_PREC}, {scale})"
45 )
46 }
47 })?;
48 Ok(Some(ret))
49 },
50 )?;
51 Ok(phys.into_decimal_unchecked(DEC128_MAX_PREC, scale))
52 }
53
54 fn apply_scaled_kernel_values(
58 &self,
59 rhs: &Self,
60 scale: usize,
61 op: &str,
62 kernel: impl Fn(i128, usize, i128, usize, usize) -> Option<i128>,
63 ) -> PolarsResult<Self> {
64 let left_s = self.scale();
65 let right_s = rhs.scale();
66 let mut failed = false;
67 let phys = broadcast_binary_elementwise_values(self.physical(), rhs.physical(), |l, r| {
68 let ret = kernel(l, left_s, r, right_s, scale);
69 failed |= ret.is_none();
70 ret.unwrap_or(0)
71 });
72 if failed {
73 return self.apply_scaled_kernel(rhs, scale, op, kernel);
74 }
75 Ok(phys.into_decimal_unchecked(DEC128_MAX_PREC, scale))
76 }
77
78 fn apply_i64_values(
81 &self,
82 rhs: &Self,
83 scale: usize,
84 op: impl Fn(i64, i64) -> i128,
85 ) -> Option<Self> {
86 let failed = Cell::new(false);
87 let to_arr = |values: Option<Vec<i128>>, validity: Option<Bitmap>| {
88 let Some(values) = values else {
89 failed.set(true);
91 return Int128Array::new_empty(ArrowDataType::Int128);
92 };
93 Int128Array::from_vec(values).with_validity(validity)
94 };
95 let phys = apply_binary_kernel_broadcast(
96 self.physical(),
97 rhs.physical(),
98 |l, r| {
99 let values = i64_binary_values(l.values(), r.values(), &op);
100 to_arr(values, combine_validities_and(l.validity(), r.validity()))
101 },
102 |l, r| {
103 let values = i64::try_from(l)
104 .ok()
105 .and_then(|l| i64_unary_values(r.values(), |r| op(l, r)));
106 to_arr(values, r.validity().cloned())
107 },
108 |l, r| {
109 let values = i64::try_from(r)
110 .ok()
111 .and_then(|r| i64_unary_values(l.values(), |l| op(l, r)));
112 to_arr(values, l.validity().cloned())
113 },
114 );
115 (!failed.get()).then(|| phys.into_decimal_unchecked(DEC128_MAX_PREC, scale))
116 }
117
118 fn scalar_with_scale(&self, scale: usize) -> Option<Self> {
120 if self.len() != 1 || self.scale() == scale {
121 return None;
122 }
123 let value = dec128_rescale(
124 self.physical().get(0)?,
125 self.scale(),
126 DEC128_MAX_PREC,
127 scale,
128 )?;
129 Some(
130 Int128Chunked::from_slice(self.name().clone(), &[value])
131 .into_decimal_unchecked(DEC128_MAX_PREC, scale),
132 )
133 }
134
135 fn add_sub(
138 &self,
139 rhs: &Self,
140 op: &str,
141 i64_op: impl Fn(i64, i64) -> i128,
142 kernel: impl Fn(i128, usize, i128, usize, usize) -> Option<i128>,
143 ) -> PolarsResult<Self> {
144 let scale = self.scale().max(rhs.scale());
145 let lhs_scalar = self.scalar_with_scale(scale);
146 let rhs_scalar = rhs.scalar_with_scale(scale);
147 let lhs = lhs_scalar.as_ref().unwrap_or(self);
148 let rhs = rhs_scalar.as_ref().unwrap_or(rhs);
149 if lhs.scale() == rhs.scale()
150 && let Some(out) = lhs.apply_i64_values(rhs, scale, i64_op)
151 {
152 return Ok(out);
153 }
154 lhs.apply_scaled_kernel_values(rhs, scale, op, kernel)
155 }
156
157 pub fn mul_with_scale(&self, rhs: &Self, scale: usize) -> PolarsResult<Self> {
159 if self.scale() + rhs.scale() == scale
160 && let Some(out) = self.apply_i64_values(rhs, scale, |l, r| l as i128 * r as i128)
161 {
162 return Ok(out);
163 }
164 self.apply_scaled_kernel_values(rhs, scale, "multiplication", dec128_mul_scaled)
165 }
166
167 pub fn div_with_scale(&self, rhs: &Self, scale: usize) -> PolarsResult<Self> {
169 self.apply_scaled_kernel(rhs, scale, "division", dec128_div_scaled)
170 }
171
172 pub fn rem_with(&self, rhs: &Self, rounding: DivRounding) -> PolarsResult<Self> {
174 let scale = self.scale().max(rhs.scale());
175 self.apply_scaled_kernel(rhs, scale, "remainder", |l, sl, r, sr, s| {
176 dec128_rem_scaled(l, sl, r, sr, s, rounding)
177 })
178 }
179
180 pub fn int_div(&self, rhs: &Self, rounding: DivRounding) -> PolarsResult<Self> {
182 self.int_div_with_scale(rhs, self.scale().max(rhs.scale()), rounding)
183 }
184
185 pub fn int_div_with_scale(
187 &self,
188 rhs: &Self,
189 scale: usize,
190 rounding: DivRounding,
191 ) -> PolarsResult<Self> {
192 self.apply_scaled_kernel(rhs, scale, "integer division", |l, sl, r, sr, s| {
193 dec128_int_div_scaled(l, sl, r, sr, s, rounding)
194 })
195 }
196}
197
198impl Add for &DecimalChunked {
199 type Output = PolarsResult<DecimalChunked>;
200
201 fn add(self, rhs: Self) -> Self::Output {
202 self.add_sub(
203 rhs,
204 "addition",
205 |l, r| l as i128 + r as i128,
206 dec128_add_scaled,
207 )
208 }
209}
210
211impl Sub for &DecimalChunked {
212 type Output = PolarsResult<DecimalChunked>;
213
214 fn sub(self, rhs: Self) -> Self::Output {
215 self.add_sub(
216 rhs,
217 "subtraction",
218 |l, r| l as i128 - r as i128,
219 dec128_sub_scaled,
220 )
221 }
222}
223
224impl Mul for &DecimalChunked {
225 type Output = PolarsResult<DecimalChunked>;
226
227 fn mul(self, rhs: Self) -> Self::Output {
228 self.mul_with_scale(rhs, self.scale().max(rhs.scale()))
229 }
230}
231
232impl Div for &DecimalChunked {
233 type Output = PolarsResult<DecimalChunked>;
234
235 fn div(self, rhs: Self) -> Self::Output {
236 self.div_with_scale(rhs, self.scale().max(rhs.scale()))
237 }
238}