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 _ => {
518 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
519 lhs.add_to(rhs.as_ref())
520 },
521 }
522 }
523}
524
525impl Sub for &Series {
526 type Output = PolarsResult<Series>;
527
528 fn sub(self, rhs: Self) -> Self::Output {
529 check_lengths(self, rhs)?;
530 match (self.dtype(), rhs.dtype()) {
531 #[cfg(feature = "dtype-struct")]
532 (DataType::Struct(_), DataType::Struct(_)) => {
533 _struct_arithmetic(self, rhs, |a, b| a.sub(b))
534 },
535 (DataType::List(_), _) | (_, DataType::List(_)) => {
536 list::NumericListOp::sub().execute(self, rhs)
537 },
538 #[cfg(feature = "dtype-array")]
539 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
540 fixed_size_list::NumericFixedSizeListOp::sub().execute(self, rhs)
541 },
542 _ => {
543 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
544 lhs.subtract(rhs.as_ref())
545 },
546 }
547 }
548}
549
550impl Mul for &Series {
551 type Output = PolarsResult<Series>;
552
553 fn mul(self, rhs: Self) -> Self::Output {
559 check_lengths(self, rhs)?;
560
561 use DataType::*;
562 match (self.dtype(), rhs.dtype()) {
563 #[cfg(feature = "dtype-struct")]
564 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.mul(b)),
565 (Duration(_), _) | (Date, _) | (Datetime(_, _), _) | (Time, _) => self.multiply(rhs),
567 (_, Date) | (_, Datetime(_, _)) | (_, Time) => {
569 polars_bail!(opq = mul, self.dtype(), rhs.dtype())
570 },
571 (_, Duration(_)) => {
572 let out = rhs.multiply(self)?;
574 Ok(out.with_name(self.name().clone()))
575 },
576 (DataType::List(_), _) | (_, DataType::List(_)) => {
577 list::NumericListOp::mul().execute(self, rhs)
578 },
579 #[cfg(feature = "dtype-array")]
580 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
581 fixed_size_list::NumericFixedSizeListOp::mul().execute(self, rhs)
582 },
583 _ => {
584 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
585 lhs.multiply(rhs.as_ref())
586 },
587 }
588 }
589}
590
591impl Div for &Series {
592 type Output = PolarsResult<Series>;
593
594 fn div(self, rhs: Self) -> Self::Output {
600 check_lengths(self, rhs)?;
601 use DataType::*;
602 match (self.dtype(), rhs.dtype()) {
603 #[cfg(feature = "dtype-struct")]
604 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.div(b)),
605 (Duration(_), _) => self.divide(rhs),
606 (Date, _)
607 | (Datetime(_, _), _)
608 | (Time, _)
609 | (_, Duration(_))
610 | (_, Time)
611 | (_, Date)
612 | (_, Datetime(_, _)) => polars_bail!(opq = div, self.dtype(), rhs.dtype()),
613 (DataType::List(_), _) | (_, DataType::List(_)) => {
614 list::NumericListOp::div().execute(self, rhs)
615 },
616 #[cfg(feature = "dtype-array")]
617 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
618 fixed_size_list::NumericFixedSizeListOp::div().execute(self, rhs)
619 },
620 _ => {
621 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
622 lhs.divide(rhs.as_ref())
623 },
624 }
625 }
626}
627
628impl Rem for &Series {
629 type Output = PolarsResult<Series>;
630
631 fn rem(self, rhs: Self) -> Self::Output {
637 check_lengths(self, rhs)?;
638 match (self.dtype(), rhs.dtype()) {
639 #[cfg(feature = "dtype-struct")]
640 (DataType::Struct(_), DataType::Struct(_)) => {
641 _struct_arithmetic(self, rhs, |a, b| a.rem(b))
642 },
643 (DataType::List(_), _) | (_, DataType::List(_)) => {
644 list::NumericListOp::rem().execute(self, rhs)
645 },
646 #[cfg(feature = "dtype-array")]
647 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
648 fixed_size_list::NumericFixedSizeListOp::rem().execute(self, rhs)
649 },
650 _ => {
651 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
652 lhs.remainder(rhs.as_ref())
653 },
654 }
655 }
656}
657
658fn finish_cast(inp: &Series, out: Series) -> Series {
661 match inp.dtype() {
662 #[cfg(feature = "dtype-date")]
663 DataType::Date => out.into_date(),
664 #[cfg(feature = "dtype-datetime")]
665 DataType::Datetime(tu, tz) => out.into_datetime(*tu, tz.clone()),
666 #[cfg(feature = "dtype-duration")]
667 DataType::Duration(tu) => out.into_duration(*tu),
668 #[cfg(feature = "dtype-time")]
669 DataType::Time => out.into_time(),
670 _ => out,
671 }
672}
673
674impl<T> Sub<T> for &Series
675where
676 T: Num + NumCast,
677{
678 type Output = Series;
679
680 fn sub(self, rhs: T) -> Self::Output {
681 let s = self.to_physical_repr();
682 macro_rules! sub {
683 ($ca:expr) => {{ $ca.sub(rhs).into_series() }};
684 }
685
686 let out = downcast_as_macro_arg_physical!(s, sub);
687 finish_cast(self, out)
688 }
689}
690
691impl<T> Sub<T> for Series
692where
693 T: Num + NumCast,
694{
695 type Output = Self;
696
697 fn sub(self, rhs: T) -> Self::Output {
698 (&self).sub(rhs)
699 }
700}
701
702impl<T> Add<T> for &Series
703where
704 T: Num + NumCast,
705{
706 type Output = Series;
707
708 fn add(self, rhs: T) -> Self::Output {
709 let s = self.to_physical_repr();
710 macro_rules! add {
711 ($ca:expr) => {{ $ca.add(rhs).into_series() }};
712 }
713 let out = downcast_as_macro_arg_physical!(s, add);
714 finish_cast(self, out)
715 }
716}
717
718impl<T> Add<T> for Series
719where
720 T: Num + NumCast,
721{
722 type Output = Self;
723
724 fn add(self, rhs: T) -> Self::Output {
725 (&self).add(rhs)
726 }
727}
728
729impl<T> Div<T> for &Series
730where
731 T: Num + NumCast,
732{
733 type Output = Series;
734
735 fn div(self, rhs: T) -> Self::Output {
736 let s = self.to_physical_repr();
737 macro_rules! div {
738 ($ca:expr) => {{ $ca.div(rhs).into_series() }};
739 }
740
741 let out = downcast_as_macro_arg_physical!(s, div);
742 finish_cast(self, out)
743 }
744}
745
746impl<T> Div<T> for Series
747where
748 T: Num + NumCast,
749{
750 type Output = Self;
751
752 fn div(self, rhs: T) -> Self::Output {
753 (&self).div(rhs)
754 }
755}
756
757impl Series {
759 pub fn wrapping_trunc_div_scalar<T: Num + NumCast>(&self, rhs: T) -> Self {
760 let s = self.to_physical_repr();
761 macro_rules! div {
762 ($ca:expr) => {{
763 let rhs = NumCast::from(rhs).unwrap();
764 $ca.wrapping_trunc_div_scalar(rhs).into_series()
765 }};
766 }
767
768 let out = downcast_as_macro_arg_physical!(s, div);
769 finish_cast(self, out)
770 }
771}
772
773impl<T> Mul<T> for &Series
774where
775 T: Num + NumCast,
776{
777 type Output = Series;
778
779 fn mul(self, rhs: T) -> Self::Output {
780 let s = self.to_physical_repr();
781 macro_rules! mul {
782 ($ca:expr) => {{ $ca.mul(rhs).into_series() }};
783 }
784 let out = downcast_as_macro_arg_physical!(s, mul);
785 finish_cast(self, out)
786 }
787}
788
789impl<T> Mul<T> for Series
790where
791 T: Num + NumCast,
792{
793 type Output = Self;
794
795 fn mul(self, rhs: T) -> Self::Output {
796 (&self).mul(rhs)
797 }
798}
799
800impl<T> Rem<T> for &Series
801where
802 T: Num + NumCast,
803{
804 type Output = Series;
805
806 fn rem(self, rhs: T) -> Self::Output {
807 let s = self.to_physical_repr();
808 macro_rules! rem {
809 ($ca:expr) => {{ $ca.rem(rhs).into_series() }};
810 }
811 let out = downcast_as_macro_arg_physical!(s, rem);
812 finish_cast(self, out)
813 }
814}
815
816impl<T> Rem<T> for Series
817where
818 T: Num + NumCast,
819{
820 type Output = Self;
821
822 fn rem(self, rhs: T) -> Self::Output {
823 (&self).rem(rhs)
824 }
825}
826
827impl<T: PolarsNumericType> ChunkedArray<T> {
831 #[must_use]
833 pub fn lhs_sub<N: Num + NumCast>(&self, lhs: N) -> Self {
834 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
835 ArithmeticChunked::wrapping_sub_scalar_lhs(lhs, self)
836 }
837
838 #[must_use]
840 pub fn lhs_div<N: Num + NumCast>(&self, lhs: N) -> Self {
841 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
842 ArithmeticChunked::legacy_div_scalar_lhs(lhs, self)
843 }
844
845 #[must_use]
847 pub fn lhs_rem<N: Num + NumCast>(&self, lhs: N) -> Self {
848 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
849 ArithmeticChunked::wrapping_mod_scalar_lhs(lhs, self)
850 }
851}
852
853pub trait LhsNumOps {
854 type Output;
855
856 fn add(self, rhs: &Series) -> Self::Output;
857 fn sub(self, rhs: &Series) -> Self::Output;
858 fn div(self, rhs: &Series) -> Self::Output;
859 fn mul(self, rhs: &Series) -> Self::Output;
860 fn rem(self, rem: &Series) -> Self::Output;
861}
862
863impl<T> LhsNumOps for T
864where
865 T: Num + NumCast,
866{
867 type Output = Series;
868
869 fn add(self, rhs: &Series) -> Self::Output {
870 rhs + self
872 }
873 fn sub(self, rhs: &Series) -> Self::Output {
874 let s = rhs.to_physical_repr();
875 macro_rules! sub {
876 ($rhs:expr) => {{ $rhs.lhs_sub(self).into_series() }};
877 }
878 let out = downcast_as_macro_arg_physical!(s, sub);
879
880 finish_cast(rhs, out)
881 }
882 fn div(self, rhs: &Series) -> Self::Output {
883 let s = rhs.to_physical_repr();
884 macro_rules! div {
885 ($rhs:expr) => {{ $rhs.lhs_div(self).into_series() }};
886 }
887 let out = downcast_as_macro_arg_physical!(s, div);
888
889 finish_cast(rhs, out)
890 }
891 fn mul(self, rhs: &Series) -> Self::Output {
892 rhs * self
894 }
895 fn rem(self, rhs: &Series) -> Self::Output {
896 let s = rhs.to_physical_repr();
897 macro_rules! rem {
898 ($rhs:expr) => {{ $rhs.lhs_rem(self).into_series() }};
899 }
900
901 let out = downcast_as_macro_arg_physical!(s, rem);
902
903 finish_cast(rhs, out)
904 }
905}
906
907#[cfg(test)]
908mod test {
909 use crate::prelude::*;
910
911 #[test]
912 #[allow(clippy::eq_op)]
913 fn test_arithmetic_series() -> PolarsResult<()> {
914 let s = Series::new("foo".into(), [1, 2, 3]);
916 assert_eq!(
917 Vec::from((&s * &s)?.i32().unwrap()),
918 [Some(1), Some(4), Some(9)]
919 );
920 assert_eq!(
921 Vec::from((&s / &s)?.i32().unwrap()),
922 [Some(1), Some(1), Some(1)]
923 );
924 assert_eq!(
925 Vec::from((&s - &s)?.i32().unwrap()),
926 [Some(0), Some(0), Some(0)]
927 );
928 assert_eq!(
929 Vec::from((&s + &s)?.i32().unwrap()),
930 [Some(2), Some(4), Some(6)]
931 );
932 assert_eq!(
934 Vec::from((&s + 1).i32().unwrap()),
935 [Some(2), Some(3), Some(4)]
936 );
937 assert_eq!(
938 Vec::from((&s - 1).i32().unwrap()),
939 [Some(0), Some(1), Some(2)]
940 );
941 assert_eq!(
942 Vec::from((&s * 2).i32().unwrap()),
943 [Some(2), Some(4), Some(6)]
944 );
945 assert_eq!(
946 Vec::from((&s / 2).i32().unwrap()),
947 [Some(0), Some(1), Some(1)]
948 );
949
950 assert_eq!(
952 Vec::from((1.add(&s)).i32().unwrap()),
953 [Some(2), Some(3), Some(4)]
954 );
955 assert_eq!(
956 Vec::from((1.sub(&s)).i32().unwrap()),
957 [Some(0), Some(-1), Some(-2)]
958 );
959 assert_eq!(
960 Vec::from((1.div(&s)).i32().unwrap()),
961 [Some(1), Some(0), Some(0)]
962 );
963 assert_eq!(
964 Vec::from((1.mul(&s)).i32().unwrap()),
965 [Some(1), Some(2), Some(3)]
966 );
967 assert_eq!(
968 Vec::from((1.rem(&s)).i32().unwrap()),
969 [Some(0), Some(1), Some(1)]
970 );
971
972 assert_eq!((&s * &s)?.name().as_str(), "foo");
973 assert_eq!((&s * 1).name().as_str(), "foo");
974 assert_eq!((1.div(&s)).name().as_str(), "foo");
975
976 Ok(())
977 }
978
979 #[test]
980 #[cfg(feature = "checked_arithmetic")]
981 fn test_checked_div() {
982 let s = Series::new("foo".into(), [1i32, 0, 1]);
983 let out = s.checked_div(&s).unwrap();
984 assert_eq!(Vec::from(out.i32().unwrap()), &[Some(1), None, Some(1)]);
985 let out = s.checked_div_num(0).unwrap();
986 assert_eq!(Vec::from(out.i32().unwrap()), &[None, None, None]);
987
988 let s_f32 = Series::new("float32".into(), [1.0f32, 0.0, 1.0]);
989 let out = s_f32.checked_div(&s_f32).unwrap();
990 assert_eq!(
991 Vec::from(out.f32().unwrap()),
992 &[Some(1.0f32), None, Some(1.0f32)]
993 );
994 let out = s_f32.checked_div_num(0.0f32).unwrap();
995 assert_eq!(Vec::from(out.f32().unwrap()), &[None, None, None]);
996
997 let s_f64 = Series::new("float64".into(), [1.0f64, 0.0, 1.0]);
998 let out = s_f64.checked_div(&s_f64).unwrap();
999 assert_eq!(
1000 Vec::from(out.f64().unwrap()),
1001 &[Some(1.0f64), None, Some(1.0f64)]
1002 );
1003 let out = s_f64.checked_div_num(0.0f64).unwrap();
1004 assert_eq!(Vec::from(out.f64().unwrap()), &[None, None, None]);
1005 }
1006}