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