1use super::*;
2use crate::utils::align_chunks_binary;
3
4pub trait NumOpsDispatchInner: PolarsDataType + Sized {
5 fn subtract(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
6 polars_bail!(opq = sub, lhs.dtype(), rhs.dtype());
7 }
8 fn add_to(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
9 polars_bail!(opq = add, lhs.dtype(), rhs.dtype());
10 }
11 fn multiply(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
12 polars_bail!(opq = mul, lhs.dtype(), rhs.dtype());
13 }
14 fn divide(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
15 polars_bail!(opq = div, lhs.dtype(), rhs.dtype());
16 }
17 fn remainder(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
18 polars_bail!(opq = rem, lhs.dtype(), rhs.dtype());
19 }
20}
21
22pub trait NumOpsDispatch {
23 fn subtract(&self, rhs: &Series) -> PolarsResult<Series>;
24 fn add_to(&self, rhs: &Series) -> PolarsResult<Series>;
25 fn multiply(&self, rhs: &Series) -> PolarsResult<Series>;
26 fn divide(&self, rhs: &Series) -> PolarsResult<Series>;
27 fn remainder(&self, rhs: &Series) -> PolarsResult<Series>;
28}
29
30impl<T: NumOpsDispatchInner> NumOpsDispatch for ChunkedArray<T> {
31 fn subtract(&self, rhs: &Series) -> PolarsResult<Series> {
32 T::subtract(self, rhs)
33 }
34 fn add_to(&self, rhs: &Series) -> PolarsResult<Series> {
35 T::add_to(self, rhs)
36 }
37 fn multiply(&self, rhs: &Series) -> PolarsResult<Series> {
38 T::multiply(self, rhs)
39 }
40 fn divide(&self, rhs: &Series) -> PolarsResult<Series> {
41 T::divide(self, rhs)
42 }
43 fn remainder(&self, rhs: &Series) -> PolarsResult<Series> {
44 T::remainder(self, rhs)
45 }
46}
47
48impl<T: PolarsNumericType> NumOpsDispatchInner for T {
49 fn subtract(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
50 polars_ensure!(
51 lhs.dtype() == rhs.dtype(),
52 opq = add,
53 rhs.dtype(),
54 rhs.dtype()
55 );
56
57 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
63 let out = lhs - rhs;
64 Ok(out.into_series())
65 }
66 fn add_to(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
67 polars_ensure!(
68 lhs.dtype() == rhs.dtype(),
69 opq = add,
70 rhs.dtype(),
71 rhs.dtype()
72 );
73
74 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
77 let out = lhs + rhs;
78 Ok(out.into_series())
79 }
80 fn multiply(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
81 polars_ensure!(
82 lhs.dtype() == rhs.dtype(),
83 opq = add,
84 rhs.dtype(),
85 rhs.dtype()
86 );
87
88 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
91 let out = lhs * rhs;
92 Ok(out.into_series())
93 }
94 fn divide(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
95 polars_ensure!(
96 lhs.dtype() == rhs.dtype(),
97 opq = add,
98 rhs.dtype(),
99 rhs.dtype()
100 );
101
102 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
105 let out = lhs / rhs;
106 Ok(out.into_series())
107 }
108 fn remainder(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
109 polars_ensure!(
110 lhs.dtype() == rhs.dtype(),
111 opq = add,
112 rhs.dtype(),
113 rhs.dtype()
114 );
115
116 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
119 let out = lhs % rhs;
120 Ok(out.into_series())
121 }
122}
123
124impl NumOpsDispatchInner for StringType {
125 fn add_to(lhs: &StringChunked, rhs: &Series) -> PolarsResult<Series> {
126 let rhs = lhs.unpack_series_matching_type(rhs)?;
127 let out = lhs + rhs;
128 Ok(out.into_series())
129 }
130}
131
132impl NumOpsDispatchInner for BinaryType {
133 fn add_to(lhs: &BinaryChunked, rhs: &Series) -> PolarsResult<Series> {
134 let rhs = lhs.unpack_series_matching_type(rhs)?;
135 let out = lhs + rhs;
136 Ok(out.into_series())
137 }
138}
139
140impl NumOpsDispatchInner for BooleanType {
141 fn add_to(lhs: &BooleanChunked, rhs: &Series) -> PolarsResult<Series> {
142 let rhs = lhs.unpack_series_matching_type(rhs)?;
143 let out = lhs + rhs;
144 Ok(out.into_series())
145 }
146}
147
148#[cfg(feature = "checked_arithmetic")]
149pub mod checked {
150 use num_traits::{CheckedDiv, One, ToPrimitive, Zero};
151
152 use super::*;
153
154 pub trait NumOpsDispatchCheckedInner: PolarsDataType + Sized {
155 fn checked_div(lhs: &ChunkedArray<Self>, rhs: &Series) -> PolarsResult<Series> {
157 polars_bail!(opq = checked_div, lhs.dtype(), rhs.dtype());
158 }
159 fn checked_div_num<T: ToPrimitive>(
160 lhs: &ChunkedArray<Self>,
161 _rhs: T,
162 ) -> PolarsResult<Series> {
163 polars_bail!(opq = checked_div_num, lhs.dtype(), Self::get_static_dtype());
164 }
165 }
166
167 pub trait NumOpsDispatchChecked {
168 fn checked_div(&self, rhs: &Series) -> PolarsResult<Series>;
170 fn checked_div_num<T: ToPrimitive>(&self, _rhs: T) -> PolarsResult<Series>;
171 }
172
173 impl<S: NumOpsDispatchCheckedInner> NumOpsDispatchChecked for ChunkedArray<S> {
174 fn checked_div(&self, rhs: &Series) -> PolarsResult<Series> {
175 S::checked_div(self, rhs)
176 }
177 fn checked_div_num<T: ToPrimitive>(&self, rhs: T) -> PolarsResult<Series> {
178 S::checked_div_num(self, rhs)
179 }
180 }
181
182 impl<T> NumOpsDispatchCheckedInner for T
183 where
184 T: PolarsIntegerType,
185 T::Native: CheckedDiv<Output = T::Native> + CheckedDiv<Output = T::Native> + Zero + One,
186 {
187 fn checked_div(lhs: &ChunkedArray<T>, rhs: &Series) -> PolarsResult<Series> {
188 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
194
195 let ca: ChunkedArray<T> =
196 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
197 (Some(l), Some(r)) => l.checked_div(&r),
198 _ => None,
199 });
200 Ok(ca.into_series())
201 }
202 }
203
204 #[cfg(feature = "dtype-f16")]
205 impl NumOpsDispatchCheckedInner for Float16Type {
206 fn checked_div(lhs: &Float16Chunked, rhs: &Series) -> PolarsResult<Series> {
207 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
210
211 let ca: Float16Chunked =
212 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
213 (Some(l), Some(r)) => {
214 if r.is_zero() {
215 None
216 } else {
217 Some(l / r)
218 }
219 },
220 _ => None,
221 });
222 Ok(ca.into_series())
223 }
224 }
225
226 impl NumOpsDispatchCheckedInner for Float32Type {
227 fn checked_div(lhs: &Float32Chunked, rhs: &Series) -> PolarsResult<Series> {
228 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
231
232 let ca: Float32Chunked =
233 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
234 (Some(l), Some(r)) => {
235 if r.is_zero() {
236 None
237 } else {
238 Some(l / r)
239 }
240 },
241 _ => None,
242 });
243 Ok(ca.into_series())
244 }
245 }
246
247 impl NumOpsDispatchCheckedInner for Float64Type {
248 fn checked_div(lhs: &Float64Chunked, rhs: &Series) -> PolarsResult<Series> {
249 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
252
253 let ca: Float64Chunked =
254 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
255 (Some(l), Some(r)) => {
256 if r.is_zero() {
257 None
258 } else {
259 Some(l / r)
260 }
261 },
262 _ => None,
263 });
264 Ok(ca.into_series())
265 }
266 }
267
268 impl NumOpsDispatchChecked for Series {
269 fn checked_div(&self, rhs: &Series) -> PolarsResult<Series> {
270 let (lhs, rhs) = coerce_lhs_rhs(self, rhs).expect("cannot coerce datatypes");
271 lhs.as_ref().as_ref().checked_div(rhs.as_ref())
272 }
273
274 fn checked_div_num<T: ToPrimitive>(&self, rhs: T) -> PolarsResult<Series> {
275 use DataType::*;
276 let s = self.to_physical_repr();
277
278 let out = match s.dtype() {
279 #[cfg(feature = "dtype-u8")]
280 UInt8 => s
281 .u8()
282 .unwrap()
283 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u8().unwrap())))
284 .into_series(),
285 #[cfg(feature = "dtype-i8")]
286 Int8 => s
287 .i8()
288 .unwrap()
289 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i8().unwrap())))
290 .into_series(),
291 #[cfg(feature = "dtype-i16")]
292 Int16 => s
293 .i16()
294 .unwrap()
295 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i16().unwrap())))
296 .into_series(),
297 #[cfg(feature = "dtype-u16")]
298 UInt16 => s
299 .u16()
300 .unwrap()
301 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u16().unwrap())))
302 .into_series(),
303 UInt32 => s
304 .u32()
305 .unwrap()
306 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u32().unwrap())))
307 .into_series(),
308 Int32 => s
309 .i32()
310 .unwrap()
311 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i32().unwrap())))
312 .into_series(),
313 UInt64 => s
314 .u64()
315 .unwrap()
316 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u64().unwrap())))
317 .into_series(),
318 Int64 => s
319 .i64()
320 .unwrap()
321 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i64().unwrap())))
322 .into_series(),
323 Float32 => s
324 .f32()
325 .unwrap()
326 .apply(|opt_v| {
327 opt_v.and_then(|v| {
328 let res = rhs.to_f32().unwrap();
329 if res.is_zero() { None } else { Some(v / res) }
330 })
331 })
332 .into_series(),
333 Float64 => s
334 .f64()
335 .unwrap()
336 .apply(|opt_v| {
337 opt_v.and_then(|v| {
338 let res = rhs.to_f64().unwrap();
339 if res.is_zero() { None } else { Some(v / res) }
340 })
341 })
342 .into_series(),
343 _ => panic!("dtype not yet supported in checked div"),
344 };
345 out.cast(self.dtype())
346 }
347 }
348}
349
350pub fn coerce_lhs_rhs<'a>(
351 lhs: &'a Series,
352 rhs: &'a Series,
353) -> PolarsResult<(Cow<'a, Series>, Cow<'a, Series>)> {
354 if let Some(result) = coerce_time_units(lhs, rhs) {
355 return Ok(result);
356 }
357 let (left_dtype, right_dtype) = (lhs.dtype(), rhs.dtype());
358 let leaf_super_dtype = try_get_supertype(left_dtype.leaf_dtype(), right_dtype.leaf_dtype())?;
359
360 let mut new_left_dtype = left_dtype.cast_leaf(leaf_super_dtype.clone());
361 let mut new_right_dtype = right_dtype.cast_leaf(leaf_super_dtype);
362
363 if left_dtype.is_list()
367 || right_dtype.is_list()
368 || left_dtype.is_array()
369 || right_dtype.is_array()
370 {
371 new_left_dtype = try_get_supertype(&new_left_dtype, &new_right_dtype)?;
372 new_right_dtype = new_left_dtype.clone();
373 }
374
375 let left = if lhs.dtype() == &new_left_dtype {
376 Cow::Borrowed(lhs)
377 } else {
378 Cow::Owned(lhs.cast(&new_left_dtype)?)
379 };
380 let right = if rhs.dtype() == &new_right_dtype {
381 Cow::Borrowed(rhs)
382 } else {
383 Cow::Owned(rhs.cast(&new_right_dtype)?)
384 };
385 Ok((left, right))
386}
387
388fn coerce_time_units<'a>(
393 lhs: &'a Series,
394 rhs: &'a Series,
395) -> Option<(Cow<'a, Series>, Cow<'a, Series>)> {
396 match (lhs.dtype(), rhs.dtype()) {
397 (DataType::Datetime(lu, t), DataType::Duration(ru)) => {
398 let units = get_time_units(lu, ru);
399 let left = if *lu == units {
400 Cow::Borrowed(lhs)
401 } else {
402 Cow::Owned(lhs.cast(&DataType::Datetime(units, t.clone())).ok()?)
403 };
404 let right = if *ru == units {
405 Cow::Borrowed(rhs)
406 } else {
407 Cow::Owned(rhs.cast(&DataType::Duration(units)).ok()?)
408 };
409 Some((left, right))
410 },
411 (DataType::Date, DataType::Duration(_)) => Some((Cow::Borrowed(lhs), Cow::Borrowed(rhs))),
413 (DataType::Duration(lu), DataType::Duration(ru)) => {
414 let units = get_time_units(lu, ru);
415 let left = if *lu == units {
416 Cow::Borrowed(lhs)
417 } else {
418 Cow::Owned(lhs.cast(&DataType::Duration(units)).ok()?)
419 };
420 let right = if *ru == units {
421 Cow::Borrowed(rhs)
422 } else {
423 Cow::Owned(rhs.cast(&DataType::Duration(units)).ok()?)
424 };
425 Some((left, right))
426 },
427 (DataType::Duration(_), DataType::Datetime(_, _))
429 | (DataType::Duration(_), DataType::Date) => {
430 let (right, left) = coerce_time_units(rhs, lhs)?;
431 Some((left, right))
432 },
433 _ => None,
434 }
435}
436
437#[cfg(feature = "dtype-struct")]
438pub fn _struct_arithmetic<F: FnMut(&Series, &Series) -> PolarsResult<Series>>(
439 s: &Series,
440 rhs: &Series,
441 mut func: F,
442) -> PolarsResult<Series> {
443 let s = s.struct_().unwrap();
444 let rhs = rhs.struct_().unwrap();
445
446 let s_fields = s.fields_as_series();
447 let rhs_fields = rhs.fields_as_series();
448
449 match (s_fields.len(), rhs_fields.len()) {
450 (_, 1) => {
451 let rhs = &rhs.fields_as_series()[0];
452 Ok(s.try_apply_fields(|s| func(s, rhs))?.into_series())
453 },
454 (1, _) => {
455 let s = &s.fields_as_series()[0];
456 Ok(rhs.try_apply_fields(|rhs| func(s, rhs))?.into_series())
457 },
458 _ => {
459 let mut s = Cow::Borrowed(s);
460 let mut rhs = Cow::Borrowed(rhs);
461
462 match (s.len(), rhs.len()) {
463 (l, r) if l == r => {},
464 (1, _) => s = Cow::Owned(s.new_from_index(0, rhs.len())),
465 (_, 1) => rhs = Cow::Owned(rhs.new_from_index(0, s.len())),
466 (l, r) => {
467 polars_bail!(ComputeError: "Struct arithmetic between different lengths {l} != {r}")
468 },
469 };
470 let (s, rhs) = align_chunks_binary(&s, &rhs);
471 let mut s = s.into_owned();
472
473 s.zip_outer_validity(rhs.as_ref());
475
476 let mut rhs_iter = rhs.fields_as_series().into_iter();
477
478 Ok(s.try_apply_fields(|s| match rhs_iter.next() {
479 Some(rhs) => func(s, &rhs),
480 None => Ok(s.clone()),
481 })?
482 .into_series())
483 },
484 }
485}
486
487fn check_lengths(a: &Series, b: &Series) -> PolarsResult<()> {
488 match (a.len(), b.len()) {
489 (1, _) | (_, 1) => Ok(()),
491 (a, b) if a == b => Ok(()),
493 (a, b) => {
495 polars_bail!(InvalidOperation: "cannot do arithmetic operation on series of different lengths: got {} and {}", a, b)
496 },
497 }
498}
499
500impl Add for &Series {
501 type Output = PolarsResult<Series>;
502
503 fn add(self, rhs: Self) -> Self::Output {
504 check_lengths(self, rhs)?;
505 match (self.dtype(), rhs.dtype()) {
506 #[cfg(feature = "dtype-struct")]
507 (DataType::Struct(_), DataType::Struct(_)) => {
508 _struct_arithmetic(self, rhs, |a, b| a.add(b))
509 },
510 (DataType::List(_), _) | (_, DataType::List(_)) => {
511 list::NumericListOp::add().execute(self, rhs)
512 },
513 #[cfg(feature = "dtype-array")]
514 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
515 fixed_size_list::NumericFixedSizeListOp::add().execute(self, rhs)
516 },
517 (l_dtype, r_dtype) if l_dtype.is_temporal() != r_dtype.is_temporal() => {
518 polars_bail!(opq = add, l_dtype, r_dtype)
519 },
520 _ => {
521 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
522 lhs.add_to(rhs.as_ref())
523 },
524 }
525 }
526}
527
528impl Sub for &Series {
529 type Output = PolarsResult<Series>;
530
531 fn sub(self, rhs: Self) -> Self::Output {
532 check_lengths(self, rhs)?;
533 match (self.dtype(), rhs.dtype()) {
534 #[cfg(feature = "dtype-struct")]
535 (DataType::Struct(_), DataType::Struct(_)) => {
536 _struct_arithmetic(self, rhs, |a, b| a.sub(b))
537 },
538 (DataType::List(_), _) | (_, DataType::List(_)) => {
539 list::NumericListOp::sub().execute(self, rhs)
540 },
541 #[cfg(feature = "dtype-array")]
542 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
543 fixed_size_list::NumericFixedSizeListOp::sub().execute(self, rhs)
544 },
545 (l_dtype, r_dtype) if l_dtype.is_temporal() != r_dtype.is_temporal() => {
546 polars_bail!(opq = sub, l_dtype, r_dtype)
547 },
548 _ => {
549 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
550 lhs.subtract(rhs.as_ref())
551 },
552 }
553 }
554}
555
556impl Mul for &Series {
557 type Output = PolarsResult<Series>;
558
559 fn mul(self, rhs: Self) -> Self::Output {
565 check_lengths(self, rhs)?;
566
567 use DataType::*;
568 match (self.dtype(), rhs.dtype()) {
569 #[cfg(feature = "dtype-struct")]
570 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.mul(b)),
571 (Duration(_), _) | (Date, _) | (Datetime(_, _), _) | (Time, _) => self.multiply(rhs),
573 (_, Date) | (_, Datetime(_, _)) | (_, Time) => {
575 polars_bail!(opq = mul, self.dtype(), rhs.dtype())
576 },
577 (_, Duration(_)) => {
578 let out = rhs.multiply(self)?;
580 Ok(out.with_name(self.name().clone()))
581 },
582 (DataType::List(_), _) | (_, DataType::List(_)) => {
583 list::NumericListOp::mul().execute(self, rhs)
584 },
585 #[cfg(feature = "dtype-array")]
586 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
587 fixed_size_list::NumericFixedSizeListOp::mul().execute(self, rhs)
588 },
589 _ => {
590 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
591 lhs.multiply(rhs.as_ref())
592 },
593 }
594 }
595}
596
597impl Div for &Series {
598 type Output = PolarsResult<Series>;
599
600 fn div(self, rhs: Self) -> Self::Output {
606 check_lengths(self, rhs)?;
607 use DataType::*;
608 match (self.dtype(), rhs.dtype()) {
609 #[cfg(feature = "dtype-struct")]
610 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.div(b)),
611 (Duration(_), _) => self.divide(rhs),
612 (Date, _)
613 | (Datetime(_, _), _)
614 | (Time, _)
615 | (_, Duration(_))
616 | (_, Time)
617 | (_, Date)
618 | (_, Datetime(_, _)) => polars_bail!(opq = div, self.dtype(), rhs.dtype()),
619 (DataType::List(_), _) | (_, DataType::List(_)) => {
620 list::NumericListOp::div().execute(self, rhs)
621 },
622 #[cfg(feature = "dtype-array")]
623 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
624 fixed_size_list::NumericFixedSizeListOp::div().execute(self, rhs)
625 },
626 _ => {
627 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
628 lhs.divide(rhs.as_ref())
629 },
630 }
631 }
632}
633
634impl Rem for &Series {
635 type Output = PolarsResult<Series>;
636
637 fn rem(self, rhs: Self) -> Self::Output {
643 check_lengths(self, rhs)?;
644 match (self.dtype(), rhs.dtype()) {
645 #[cfg(feature = "dtype-struct")]
646 (DataType::Struct(_), DataType::Struct(_)) => {
647 _struct_arithmetic(self, rhs, |a, b| a.rem(b))
648 },
649 (DataType::List(_), _) | (_, DataType::List(_)) => {
650 list::NumericListOp::rem().execute(self, rhs)
651 },
652 #[cfg(feature = "dtype-array")]
653 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
654 fixed_size_list::NumericFixedSizeListOp::rem().execute(self, rhs)
655 },
656 _ => {
657 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
658 lhs.remainder(rhs.as_ref())
659 },
660 }
661 }
662}
663
664fn finish_cast(inp: &Series, out: Series) -> Series {
667 match inp.dtype() {
668 #[cfg(feature = "dtype-date")]
669 DataType::Date => out.into_date(),
670 #[cfg(feature = "dtype-datetime")]
671 DataType::Datetime(tu, tz) => out.into_datetime(*tu, tz.clone()),
672 #[cfg(feature = "dtype-duration")]
673 DataType::Duration(tu) => out.into_duration(*tu),
674 #[cfg(feature = "dtype-time")]
675 DataType::Time => out.into_time(),
676 _ => out,
677 }
678}
679
680impl<T> Sub<T> for &Series
681where
682 T: Num + NumCast,
683{
684 type Output = Series;
685
686 fn sub(self, rhs: T) -> Self::Output {
687 let s = self.to_physical_repr();
688 macro_rules! sub {
689 ($ca:expr) => {{ $ca.sub(rhs).into_series() }};
690 }
691
692 let out = downcast_as_macro_arg_physical!(s, sub);
693 finish_cast(self, out)
694 }
695}
696
697impl<T> Sub<T> for Series
698where
699 T: Num + NumCast,
700{
701 type Output = Self;
702
703 fn sub(self, rhs: T) -> Self::Output {
704 (&self).sub(rhs)
705 }
706}
707
708impl<T> Add<T> for &Series
709where
710 T: Num + NumCast,
711{
712 type Output = Series;
713
714 fn add(self, rhs: T) -> Self::Output {
715 let s = self.to_physical_repr();
716 macro_rules! add {
717 ($ca:expr) => {{ $ca.add(rhs).into_series() }};
718 }
719 let out = downcast_as_macro_arg_physical!(s, add);
720 finish_cast(self, out)
721 }
722}
723
724impl<T> Add<T> for Series
725where
726 T: Num + NumCast,
727{
728 type Output = Self;
729
730 fn add(self, rhs: T) -> Self::Output {
731 (&self).add(rhs)
732 }
733}
734
735impl<T> Div<T> for &Series
736where
737 T: Num + NumCast,
738{
739 type Output = Series;
740
741 fn div(self, rhs: T) -> Self::Output {
742 let s = self.to_physical_repr();
743 macro_rules! div {
744 ($ca:expr) => {{ $ca.div(rhs).into_series() }};
745 }
746
747 let out = downcast_as_macro_arg_physical!(s, div);
748 finish_cast(self, out)
749 }
750}
751
752impl<T> Div<T> for Series
753where
754 T: Num + NumCast,
755{
756 type Output = Self;
757
758 fn div(self, rhs: T) -> Self::Output {
759 (&self).div(rhs)
760 }
761}
762
763impl Series {
765 pub fn wrapping_trunc_div_scalar<T: Num + NumCast>(&self, rhs: T) -> Self {
766 let s = self.to_physical_repr();
767 macro_rules! div {
768 ($ca:expr) => {{
769 let rhs = NumCast::from(rhs).unwrap();
770 $ca.wrapping_trunc_div_scalar(rhs).into_series()
771 }};
772 }
773
774 let out = downcast_as_macro_arg_physical!(s, div);
775 finish_cast(self, out)
776 }
777}
778
779impl<T> Mul<T> for &Series
780where
781 T: Num + NumCast,
782{
783 type Output = Series;
784
785 fn mul(self, rhs: T) -> Self::Output {
786 let s = self.to_physical_repr();
787 macro_rules! mul {
788 ($ca:expr) => {{ $ca.mul(rhs).into_series() }};
789 }
790 let out = downcast_as_macro_arg_physical!(s, mul);
791 finish_cast(self, out)
792 }
793}
794
795impl<T> Mul<T> for Series
796where
797 T: Num + NumCast,
798{
799 type Output = Self;
800
801 fn mul(self, rhs: T) -> Self::Output {
802 (&self).mul(rhs)
803 }
804}
805
806impl<T> Rem<T> for &Series
807where
808 T: Num + NumCast,
809{
810 type Output = Series;
811
812 fn rem(self, rhs: T) -> Self::Output {
813 let s = self.to_physical_repr();
814 macro_rules! rem {
815 ($ca:expr) => {{ $ca.rem(rhs).into_series() }};
816 }
817 let out = downcast_as_macro_arg_physical!(s, rem);
818 finish_cast(self, out)
819 }
820}
821
822impl<T> Rem<T> for Series
823where
824 T: Num + NumCast,
825{
826 type Output = Self;
827
828 fn rem(self, rhs: T) -> Self::Output {
829 (&self).rem(rhs)
830 }
831}
832
833impl<T: PolarsNumericType> ChunkedArray<T> {
837 #[must_use]
839 pub fn lhs_sub<N: Num + NumCast>(&self, lhs: N) -> Self {
840 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
841 ArithmeticChunked::wrapping_sub_scalar_lhs(lhs, self)
842 }
843
844 #[must_use]
846 pub fn lhs_div<N: Num + NumCast>(&self, lhs: N) -> Self {
847 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
848 ArithmeticChunked::legacy_div_scalar_lhs(lhs, self)
849 }
850
851 #[must_use]
853 pub fn lhs_rem<N: Num + NumCast>(&self, lhs: N) -> Self {
854 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
855 ArithmeticChunked::wrapping_mod_scalar_lhs(lhs, self)
856 }
857}
858
859pub trait LhsNumOps {
860 type Output;
861
862 fn add(self, rhs: &Series) -> Self::Output;
863 fn sub(self, rhs: &Series) -> Self::Output;
864 fn div(self, rhs: &Series) -> Self::Output;
865 fn mul(self, rhs: &Series) -> Self::Output;
866 fn rem(self, rem: &Series) -> Self::Output;
867}
868
869impl<T> LhsNumOps for T
870where
871 T: Num + NumCast,
872{
873 type Output = Series;
874
875 fn add(self, rhs: &Series) -> Self::Output {
876 rhs + self
878 }
879 fn sub(self, rhs: &Series) -> Self::Output {
880 let s = rhs.to_physical_repr();
881 macro_rules! sub {
882 ($rhs:expr) => {{ $rhs.lhs_sub(self).into_series() }};
883 }
884 let out = downcast_as_macro_arg_physical!(s, sub);
885
886 finish_cast(rhs, out)
887 }
888 fn div(self, rhs: &Series) -> Self::Output {
889 let s = rhs.to_physical_repr();
890 macro_rules! div {
891 ($rhs:expr) => {{ $rhs.lhs_div(self).into_series() }};
892 }
893 let out = downcast_as_macro_arg_physical!(s, div);
894
895 finish_cast(rhs, out)
896 }
897 fn mul(self, rhs: &Series) -> Self::Output {
898 rhs * self
900 }
901 fn rem(self, rhs: &Series) -> Self::Output {
902 let s = rhs.to_physical_repr();
903 macro_rules! rem {
904 ($rhs:expr) => {{ $rhs.lhs_rem(self).into_series() }};
905 }
906
907 let out = downcast_as_macro_arg_physical!(s, rem);
908
909 finish_cast(rhs, out)
910 }
911}
912
913#[cfg(test)]
914mod test {
915 use crate::prelude::*;
916
917 #[test]
918 #[allow(clippy::eq_op)]
919 fn test_arithmetic_series() -> PolarsResult<()> {
920 let s = Series::new("foo".into(), [1, 2, 3]);
922 assert_eq!(
923 Vec::from((&s * &s)?.i32().unwrap()),
924 [Some(1), Some(4), Some(9)]
925 );
926 assert_eq!(
927 Vec::from((&s / &s)?.i32().unwrap()),
928 [Some(1), Some(1), Some(1)]
929 );
930 assert_eq!(
931 Vec::from((&s - &s)?.i32().unwrap()),
932 [Some(0), Some(0), Some(0)]
933 );
934 assert_eq!(
935 Vec::from((&s + &s)?.i32().unwrap()),
936 [Some(2), Some(4), Some(6)]
937 );
938 assert_eq!(
940 Vec::from((&s + 1).i32().unwrap()),
941 [Some(2), Some(3), Some(4)]
942 );
943 assert_eq!(
944 Vec::from((&s - 1).i32().unwrap()),
945 [Some(0), Some(1), Some(2)]
946 );
947 assert_eq!(
948 Vec::from((&s * 2).i32().unwrap()),
949 [Some(2), Some(4), Some(6)]
950 );
951 assert_eq!(
952 Vec::from((&s / 2).i32().unwrap()),
953 [Some(0), Some(1), Some(1)]
954 );
955
956 assert_eq!(
958 Vec::from((1.add(&s)).i32().unwrap()),
959 [Some(2), Some(3), Some(4)]
960 );
961 assert_eq!(
962 Vec::from((1.sub(&s)).i32().unwrap()),
963 [Some(0), Some(-1), Some(-2)]
964 );
965 assert_eq!(
966 Vec::from((1.div(&s)).i32().unwrap()),
967 [Some(1), Some(0), Some(0)]
968 );
969 assert_eq!(
970 Vec::from((1.mul(&s)).i32().unwrap()),
971 [Some(1), Some(2), Some(3)]
972 );
973 assert_eq!(
974 Vec::from((1.rem(&s)).i32().unwrap()),
975 [Some(0), Some(1), Some(1)]
976 );
977
978 assert_eq!((&s * &s)?.name().as_str(), "foo");
979 assert_eq!((&s * 1).name().as_str(), "foo");
980 assert_eq!((1.div(&s)).name().as_str(), "foo");
981
982 Ok(())
983 }
984
985 #[test]
986 #[cfg(feature = "checked_arithmetic")]
987 fn test_checked_div() {
988 let s = Series::new("foo".into(), [1i32, 0, 1]);
989 let out = s.checked_div(&s).unwrap();
990 assert_eq!(Vec::from(out.i32().unwrap()), &[Some(1), None, Some(1)]);
991 let out = s.checked_div_num(0).unwrap();
992 assert_eq!(Vec::from(out.i32().unwrap()), &[None, None, None]);
993
994 let s_f32 = Series::new("float32".into(), [1.0f32, 0.0, 1.0]);
995 let out = s_f32.checked_div(&s_f32).unwrap();
996 assert_eq!(
997 Vec::from(out.f32().unwrap()),
998 &[Some(1.0f32), None, Some(1.0f32)]
999 );
1000 let out = s_f32.checked_div_num(0.0f32).unwrap();
1001 assert_eq!(Vec::from(out.f32().unwrap()), &[None, None, None]);
1002
1003 let s_f64 = Series::new("float64".into(), [1.0f64, 0.0, 1.0]);
1004 let out = s_f64.checked_div(&s_f64).unwrap();
1005 assert_eq!(
1006 Vec::from(out.f64().unwrap()),
1007 &[Some(1.0f64), None, Some(1.0f64)]
1008 );
1009 let out = s_f64.checked_div_num(0.0f64).unwrap();
1010 assert_eq!(Vec::from(out.f64().unwrap()), &[None, None, None]);
1011 }
1012}