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 impl NumOpsDispatchCheckedInner for Float32Type {
205 fn checked_div(lhs: &Float32Chunked, rhs: &Series) -> PolarsResult<Series> {
206 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
209
210 let ca: Float32Chunked =
211 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
212 (Some(l), Some(r)) => {
213 if r.is_zero() {
214 None
215 } else {
216 Some(l / r)
217 }
218 },
219 _ => None,
220 });
221 Ok(ca.into_series())
222 }
223 }
224
225 impl NumOpsDispatchCheckedInner for Float64Type {
226 fn checked_div(lhs: &Float64Chunked, rhs: &Series) -> PolarsResult<Series> {
227 let rhs = unsafe { lhs.unpack_series_matching_physical_type(rhs) };
230
231 let ca: Float64Chunked =
232 arity::binary_elementwise(lhs, rhs, |opt_l, opt_r| match (opt_l, opt_r) {
233 (Some(l), Some(r)) => {
234 if r.is_zero() {
235 None
236 } else {
237 Some(l / r)
238 }
239 },
240 _ => None,
241 });
242 Ok(ca.into_series())
243 }
244 }
245
246 impl NumOpsDispatchChecked for Series {
247 fn checked_div(&self, rhs: &Series) -> PolarsResult<Series> {
248 let (lhs, rhs) = coerce_lhs_rhs(self, rhs).expect("cannot coerce datatypes");
249 lhs.as_ref().as_ref().checked_div(rhs.as_ref())
250 }
251
252 fn checked_div_num<T: ToPrimitive>(&self, rhs: T) -> PolarsResult<Series> {
253 use DataType::*;
254 let s = self.to_physical_repr();
255
256 let out = match s.dtype() {
257 #[cfg(feature = "dtype-u8")]
258 UInt8 => s
259 .u8()
260 .unwrap()
261 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u8().unwrap())))
262 .into_series(),
263 #[cfg(feature = "dtype-i8")]
264 Int8 => s
265 .i8()
266 .unwrap()
267 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i8().unwrap())))
268 .into_series(),
269 #[cfg(feature = "dtype-i16")]
270 Int16 => s
271 .i16()
272 .unwrap()
273 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i16().unwrap())))
274 .into_series(),
275 #[cfg(feature = "dtype-u16")]
276 UInt16 => s
277 .u16()
278 .unwrap()
279 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u16().unwrap())))
280 .into_series(),
281 UInt32 => s
282 .u32()
283 .unwrap()
284 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u32().unwrap())))
285 .into_series(),
286 Int32 => s
287 .i32()
288 .unwrap()
289 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i32().unwrap())))
290 .into_series(),
291 UInt64 => s
292 .u64()
293 .unwrap()
294 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_u64().unwrap())))
295 .into_series(),
296 Int64 => s
297 .i64()
298 .unwrap()
299 .apply(|opt_v| opt_v.and_then(|v| v.checked_div(rhs.to_i64().unwrap())))
300 .into_series(),
301 Float32 => s
302 .f32()
303 .unwrap()
304 .apply(|opt_v| {
305 opt_v.and_then(|v| {
306 let res = rhs.to_f32().unwrap();
307 if res.is_zero() { None } else { Some(v / res) }
308 })
309 })
310 .into_series(),
311 Float64 => s
312 .f64()
313 .unwrap()
314 .apply(|opt_v| {
315 opt_v.and_then(|v| {
316 let res = rhs.to_f64().unwrap();
317 if res.is_zero() { None } else { Some(v / res) }
318 })
319 })
320 .into_series(),
321 _ => panic!("dtype not yet supported in checked div"),
322 };
323 out.cast(self.dtype())
324 }
325 }
326}
327
328pub fn coerce_lhs_rhs<'a>(
329 lhs: &'a Series,
330 rhs: &'a Series,
331) -> PolarsResult<(Cow<'a, Series>, Cow<'a, Series>)> {
332 if let Some(result) = coerce_time_units(lhs, rhs) {
333 return Ok(result);
334 }
335 let (left_dtype, right_dtype) = (lhs.dtype(), rhs.dtype());
336 let leaf_super_dtype = try_get_supertype(left_dtype.leaf_dtype(), right_dtype.leaf_dtype())?;
337
338 let mut new_left_dtype = left_dtype.cast_leaf(leaf_super_dtype.clone());
339 let mut new_right_dtype = right_dtype.cast_leaf(leaf_super_dtype);
340
341 if left_dtype.is_list()
345 || right_dtype.is_list()
346 || left_dtype.is_array()
347 || right_dtype.is_array()
348 {
349 new_left_dtype = try_get_supertype(&new_left_dtype, &new_right_dtype)?;
350 new_right_dtype = new_left_dtype.clone();
351 }
352
353 let left = if lhs.dtype() == &new_left_dtype {
354 Cow::Borrowed(lhs)
355 } else {
356 Cow::Owned(lhs.cast(&new_left_dtype)?)
357 };
358 let right = if rhs.dtype() == &new_right_dtype {
359 Cow::Borrowed(rhs)
360 } else {
361 Cow::Owned(rhs.cast(&new_right_dtype)?)
362 };
363 Ok((left, right))
364}
365
366fn coerce_time_units<'a>(
371 lhs: &'a Series,
372 rhs: &'a Series,
373) -> Option<(Cow<'a, Series>, Cow<'a, Series>)> {
374 match (lhs.dtype(), rhs.dtype()) {
375 (DataType::Datetime(lu, t), DataType::Duration(ru)) => {
376 let units = get_time_units(lu, ru);
377 let left = if *lu == units {
378 Cow::Borrowed(lhs)
379 } else {
380 Cow::Owned(lhs.cast(&DataType::Datetime(units, t.clone())).ok()?)
381 };
382 let right = if *ru == units {
383 Cow::Borrowed(rhs)
384 } else {
385 Cow::Owned(rhs.cast(&DataType::Duration(units)).ok()?)
386 };
387 Some((left, right))
388 },
389 (DataType::Date, DataType::Duration(_)) => Some((Cow::Borrowed(lhs), Cow::Borrowed(rhs))),
391 (DataType::Duration(lu), DataType::Duration(ru)) => {
392 let units = get_time_units(lu, ru);
393 let left = if *lu == units {
394 Cow::Borrowed(lhs)
395 } else {
396 Cow::Owned(lhs.cast(&DataType::Duration(units)).ok()?)
397 };
398 let right = if *ru == units {
399 Cow::Borrowed(rhs)
400 } else {
401 Cow::Owned(rhs.cast(&DataType::Duration(units)).ok()?)
402 };
403 Some((left, right))
404 },
405 (DataType::Duration(_), DataType::Datetime(_, _))
407 | (DataType::Duration(_), DataType::Date) => {
408 let (right, left) = coerce_time_units(rhs, lhs)?;
409 Some((left, right))
410 },
411 _ => None,
412 }
413}
414
415#[cfg(feature = "dtype-struct")]
416pub fn _struct_arithmetic<F: FnMut(&Series, &Series) -> PolarsResult<Series>>(
417 s: &Series,
418 rhs: &Series,
419 mut func: F,
420) -> PolarsResult<Series> {
421 let s = s.struct_().unwrap();
422 let rhs = rhs.struct_().unwrap();
423
424 let s_fields = s.fields_as_series();
425 let rhs_fields = rhs.fields_as_series();
426
427 match (s_fields.len(), rhs_fields.len()) {
428 (_, 1) => {
429 let rhs = &rhs.fields_as_series()[0];
430 Ok(s.try_apply_fields(|s| func(s, rhs))?.into_series())
431 },
432 (1, _) => {
433 let s = &s.fields_as_series()[0];
434 Ok(rhs.try_apply_fields(|rhs| func(s, rhs))?.into_series())
435 },
436 _ => {
437 let mut s = Cow::Borrowed(s);
438 let mut rhs = Cow::Borrowed(rhs);
439
440 match (s.len(), rhs.len()) {
441 (l, r) if l == r => {},
442 (1, _) => s = Cow::Owned(s.new_from_index(0, rhs.len())),
443 (_, 1) => rhs = Cow::Owned(rhs.new_from_index(0, s.len())),
444 (l, r) => {
445 polars_bail!(ComputeError: "Struct arithmetic between different lengths {l} != {r}")
446 },
447 };
448 let (s, rhs) = align_chunks_binary(&s, &rhs);
449 let mut s = s.into_owned();
450
451 s.zip_outer_validity(rhs.as_ref());
453
454 let mut rhs_iter = rhs.fields_as_series().into_iter();
455
456 Ok(s.try_apply_fields(|s| match rhs_iter.next() {
457 Some(rhs) => func(s, &rhs),
458 None => Ok(s.clone()),
459 })?
460 .into_series())
461 },
462 }
463}
464
465fn check_lengths(a: &Series, b: &Series) -> PolarsResult<()> {
466 match (a.len(), b.len()) {
467 (1, _) | (_, 1) => Ok(()),
469 (a, b) if a == b => Ok(()),
471 (a, b) => {
473 polars_bail!(InvalidOperation: "cannot do arithmetic operation on series of different lengths: got {} and {}", a, b)
474 },
475 }
476}
477
478impl Add for &Series {
479 type Output = PolarsResult<Series>;
480
481 fn add(self, rhs: Self) -> Self::Output {
482 check_lengths(self, rhs)?;
483 match (self.dtype(), rhs.dtype()) {
484 #[cfg(feature = "dtype-struct")]
485 (DataType::Struct(_), DataType::Struct(_)) => {
486 _struct_arithmetic(self, rhs, |a, b| a.add(b))
487 },
488 (DataType::List(_), _) | (_, DataType::List(_)) => {
489 list::NumericListOp::add().execute(self, rhs)
490 },
491 #[cfg(feature = "dtype-array")]
492 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
493 fixed_size_list::NumericFixedSizeListOp::add().execute(self, rhs)
494 },
495 _ => {
496 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
497 lhs.add_to(rhs.as_ref())
498 },
499 }
500 }
501}
502
503impl Sub for &Series {
504 type Output = PolarsResult<Series>;
505
506 fn sub(self, rhs: Self) -> Self::Output {
507 check_lengths(self, rhs)?;
508 match (self.dtype(), rhs.dtype()) {
509 #[cfg(feature = "dtype-struct")]
510 (DataType::Struct(_), DataType::Struct(_)) => {
511 _struct_arithmetic(self, rhs, |a, b| a.sub(b))
512 },
513 (DataType::List(_), _) | (_, DataType::List(_)) => {
514 list::NumericListOp::sub().execute(self, rhs)
515 },
516 #[cfg(feature = "dtype-array")]
517 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
518 fixed_size_list::NumericFixedSizeListOp::sub().execute(self, rhs)
519 },
520 _ => {
521 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
522 lhs.subtract(rhs.as_ref())
523 },
524 }
525 }
526}
527
528impl Mul for &Series {
529 type Output = PolarsResult<Series>;
530
531 fn mul(self, rhs: Self) -> Self::Output {
537 check_lengths(self, rhs)?;
538
539 use DataType::*;
540 match (self.dtype(), rhs.dtype()) {
541 #[cfg(feature = "dtype-struct")]
542 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.mul(b)),
543 (Duration(_), _) | (Date, _) | (Datetime(_, _), _) | (Time, _) => self.multiply(rhs),
545 (_, Date) | (_, Datetime(_, _)) | (_, Time) => {
547 polars_bail!(opq = mul, self.dtype(), rhs.dtype())
548 },
549 (_, Duration(_)) => {
550 let out = rhs.multiply(self)?;
552 Ok(out.with_name(self.name().clone()))
553 },
554 (DataType::List(_), _) | (_, DataType::List(_)) => {
555 list::NumericListOp::mul().execute(self, rhs)
556 },
557 #[cfg(feature = "dtype-array")]
558 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
559 fixed_size_list::NumericFixedSizeListOp::mul().execute(self, rhs)
560 },
561 _ => {
562 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
563 lhs.multiply(rhs.as_ref())
564 },
565 }
566 }
567}
568
569impl Div for &Series {
570 type Output = PolarsResult<Series>;
571
572 fn div(self, rhs: Self) -> Self::Output {
578 check_lengths(self, rhs)?;
579 use DataType::*;
580 match (self.dtype(), rhs.dtype()) {
581 #[cfg(feature = "dtype-struct")]
582 (Struct(_), Struct(_)) => _struct_arithmetic(self, rhs, |a, b| a.div(b)),
583 (Duration(_), _) => self.divide(rhs),
584 (Date, _)
585 | (Datetime(_, _), _)
586 | (Time, _)
587 | (_, Duration(_))
588 | (_, Time)
589 | (_, Date)
590 | (_, Datetime(_, _)) => polars_bail!(opq = div, self.dtype(), rhs.dtype()),
591 (DataType::List(_), _) | (_, DataType::List(_)) => {
592 list::NumericListOp::div().execute(self, rhs)
593 },
594 #[cfg(feature = "dtype-array")]
595 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
596 fixed_size_list::NumericFixedSizeListOp::div().execute(self, rhs)
597 },
598 _ => {
599 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
600 lhs.divide(rhs.as_ref())
601 },
602 }
603 }
604}
605
606impl Rem for &Series {
607 type Output = PolarsResult<Series>;
608
609 fn rem(self, rhs: Self) -> Self::Output {
615 check_lengths(self, rhs)?;
616 match (self.dtype(), rhs.dtype()) {
617 #[cfg(feature = "dtype-struct")]
618 (DataType::Struct(_), DataType::Struct(_)) => {
619 _struct_arithmetic(self, rhs, |a, b| a.rem(b))
620 },
621 (DataType::List(_), _) | (_, DataType::List(_)) => {
622 list::NumericListOp::rem().execute(self, rhs)
623 },
624 #[cfg(feature = "dtype-array")]
625 (DataType::Array(..), _) | (_, DataType::Array(..)) => {
626 fixed_size_list::NumericFixedSizeListOp::rem().execute(self, rhs)
627 },
628 _ => {
629 let (lhs, rhs) = coerce_lhs_rhs(self, rhs)?;
630 lhs.remainder(rhs.as_ref())
631 },
632 }
633 }
634}
635
636fn finish_cast(inp: &Series, out: Series) -> Series {
639 match inp.dtype() {
640 #[cfg(feature = "dtype-date")]
641 DataType::Date => out.into_date(),
642 #[cfg(feature = "dtype-datetime")]
643 DataType::Datetime(tu, tz) => out.into_datetime(*tu, tz.clone()),
644 #[cfg(feature = "dtype-duration")]
645 DataType::Duration(tu) => out.into_duration(*tu),
646 #[cfg(feature = "dtype-time")]
647 DataType::Time => out.into_time(),
648 _ => out,
649 }
650}
651
652impl<T> Sub<T> for &Series
653where
654 T: Num + NumCast,
655{
656 type Output = Series;
657
658 fn sub(self, rhs: T) -> Self::Output {
659 let s = self.to_physical_repr();
660 macro_rules! sub {
661 ($ca:expr) => {{ $ca.sub(rhs).into_series() }};
662 }
663
664 let out = downcast_as_macro_arg_physical!(s, sub);
665 finish_cast(self, out)
666 }
667}
668
669impl<T> Sub<T> for Series
670where
671 T: Num + NumCast,
672{
673 type Output = Self;
674
675 fn sub(self, rhs: T) -> Self::Output {
676 (&self).sub(rhs)
677 }
678}
679
680impl<T> Add<T> for &Series
681where
682 T: Num + NumCast,
683{
684 type Output = Series;
685
686 fn add(self, rhs: T) -> Self::Output {
687 let s = self.to_physical_repr();
688 macro_rules! add {
689 ($ca:expr) => {{ $ca.add(rhs).into_series() }};
690 }
691 let out = downcast_as_macro_arg_physical!(s, add);
692 finish_cast(self, out)
693 }
694}
695
696impl<T> Add<T> for Series
697where
698 T: Num + NumCast,
699{
700 type Output = Self;
701
702 fn add(self, rhs: T) -> Self::Output {
703 (&self).add(rhs)
704 }
705}
706
707impl<T> Div<T> for &Series
708where
709 T: Num + NumCast,
710{
711 type Output = Series;
712
713 fn div(self, rhs: T) -> Self::Output {
714 let s = self.to_physical_repr();
715 macro_rules! div {
716 ($ca:expr) => {{ $ca.div(rhs).into_series() }};
717 }
718
719 let out = downcast_as_macro_arg_physical!(s, div);
720 finish_cast(self, out)
721 }
722}
723
724impl<T> Div<T> for Series
725where
726 T: Num + NumCast,
727{
728 type Output = Self;
729
730 fn div(self, rhs: T) -> Self::Output {
731 (&self).div(rhs)
732 }
733}
734
735impl Series {
737 pub fn wrapping_trunc_div_scalar<T: Num + NumCast>(&self, rhs: T) -> Self {
738 let s = self.to_physical_repr();
739 macro_rules! div {
740 ($ca:expr) => {{
741 let rhs = NumCast::from(rhs).unwrap();
742 $ca.wrapping_trunc_div_scalar(rhs).into_series()
743 }};
744 }
745
746 let out = downcast_as_macro_arg_physical!(s, div);
747 finish_cast(self, out)
748 }
749}
750
751impl<T> Mul<T> for &Series
752where
753 T: Num + NumCast,
754{
755 type Output = Series;
756
757 fn mul(self, rhs: T) -> Self::Output {
758 let s = self.to_physical_repr();
759 macro_rules! mul {
760 ($ca:expr) => {{ $ca.mul(rhs).into_series() }};
761 }
762 let out = downcast_as_macro_arg_physical!(s, mul);
763 finish_cast(self, out)
764 }
765}
766
767impl<T> Mul<T> for Series
768where
769 T: Num + NumCast,
770{
771 type Output = Self;
772
773 fn mul(self, rhs: T) -> Self::Output {
774 (&self).mul(rhs)
775 }
776}
777
778impl<T> Rem<T> for &Series
779where
780 T: Num + NumCast,
781{
782 type Output = Series;
783
784 fn rem(self, rhs: T) -> Self::Output {
785 let s = self.to_physical_repr();
786 macro_rules! rem {
787 ($ca:expr) => {{ $ca.rem(rhs).into_series() }};
788 }
789 let out = downcast_as_macro_arg_physical!(s, rem);
790 finish_cast(self, out)
791 }
792}
793
794impl<T> Rem<T> for Series
795where
796 T: Num + NumCast,
797{
798 type Output = Self;
799
800 fn rem(self, rhs: T) -> Self::Output {
801 (&self).rem(rhs)
802 }
803}
804
805impl<T: PolarsNumericType> ChunkedArray<T> {
809 #[must_use]
811 pub fn lhs_sub<N: Num + NumCast>(&self, lhs: N) -> Self {
812 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
813 ArithmeticChunked::wrapping_sub_scalar_lhs(lhs, self)
814 }
815
816 #[must_use]
818 pub fn lhs_div<N: Num + NumCast>(&self, lhs: N) -> Self {
819 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
820 ArithmeticChunked::legacy_div_scalar_lhs(lhs, self)
821 }
822
823 #[must_use]
825 pub fn lhs_rem<N: Num + NumCast>(&self, lhs: N) -> Self {
826 let lhs: T::Native = NumCast::from(lhs).expect("could not cast");
827 ArithmeticChunked::wrapping_mod_scalar_lhs(lhs, self)
828 }
829}
830
831pub trait LhsNumOps {
832 type Output;
833
834 fn add(self, rhs: &Series) -> Self::Output;
835 fn sub(self, rhs: &Series) -> Self::Output;
836 fn div(self, rhs: &Series) -> Self::Output;
837 fn mul(self, rhs: &Series) -> Self::Output;
838 fn rem(self, rem: &Series) -> Self::Output;
839}
840
841impl<T> LhsNumOps for T
842where
843 T: Num + NumCast,
844{
845 type Output = Series;
846
847 fn add(self, rhs: &Series) -> Self::Output {
848 rhs + self
850 }
851 fn sub(self, rhs: &Series) -> Self::Output {
852 let s = rhs.to_physical_repr();
853 macro_rules! sub {
854 ($rhs:expr) => {{ $rhs.lhs_sub(self).into_series() }};
855 }
856 let out = downcast_as_macro_arg_physical!(s, sub);
857
858 finish_cast(rhs, out)
859 }
860 fn div(self, rhs: &Series) -> Self::Output {
861 let s = rhs.to_physical_repr();
862 macro_rules! div {
863 ($rhs:expr) => {{ $rhs.lhs_div(self).into_series() }};
864 }
865 let out = downcast_as_macro_arg_physical!(s, div);
866
867 finish_cast(rhs, out)
868 }
869 fn mul(self, rhs: &Series) -> Self::Output {
870 rhs * self
872 }
873 fn rem(self, rhs: &Series) -> Self::Output {
874 let s = rhs.to_physical_repr();
875 macro_rules! rem {
876 ($rhs:expr) => {{ $rhs.lhs_rem(self).into_series() }};
877 }
878
879 let out = downcast_as_macro_arg_physical!(s, rem);
880
881 finish_cast(rhs, out)
882 }
883}
884
885#[cfg(test)]
886mod test {
887 use crate::prelude::*;
888
889 #[test]
890 #[allow(clippy::eq_op)]
891 fn test_arithmetic_series() -> PolarsResult<()> {
892 let s = Series::new("foo".into(), [1, 2, 3]);
894 assert_eq!(
895 Vec::from((&s * &s)?.i32().unwrap()),
896 [Some(1), Some(4), Some(9)]
897 );
898 assert_eq!(
899 Vec::from((&s / &s)?.i32().unwrap()),
900 [Some(1), Some(1), Some(1)]
901 );
902 assert_eq!(
903 Vec::from((&s - &s)?.i32().unwrap()),
904 [Some(0), Some(0), Some(0)]
905 );
906 assert_eq!(
907 Vec::from((&s + &s)?.i32().unwrap()),
908 [Some(2), Some(4), Some(6)]
909 );
910 assert_eq!(
912 Vec::from((&s + 1).i32().unwrap()),
913 [Some(2), Some(3), Some(4)]
914 );
915 assert_eq!(
916 Vec::from((&s - 1).i32().unwrap()),
917 [Some(0), Some(1), Some(2)]
918 );
919 assert_eq!(
920 Vec::from((&s * 2).i32().unwrap()),
921 [Some(2), Some(4), Some(6)]
922 );
923 assert_eq!(
924 Vec::from((&s / 2).i32().unwrap()),
925 [Some(0), Some(1), Some(1)]
926 );
927
928 assert_eq!(
930 Vec::from((1.add(&s)).i32().unwrap()),
931 [Some(2), Some(3), Some(4)]
932 );
933 assert_eq!(
934 Vec::from((1.sub(&s)).i32().unwrap()),
935 [Some(0), Some(-1), Some(-2)]
936 );
937 assert_eq!(
938 Vec::from((1.div(&s)).i32().unwrap()),
939 [Some(1), Some(0), Some(0)]
940 );
941 assert_eq!(
942 Vec::from((1.mul(&s)).i32().unwrap()),
943 [Some(1), Some(2), Some(3)]
944 );
945 assert_eq!(
946 Vec::from((1.rem(&s)).i32().unwrap()),
947 [Some(0), Some(1), Some(1)]
948 );
949
950 assert_eq!((&s * &s)?.name().as_str(), "foo");
951 assert_eq!((&s * 1).name().as_str(), "foo");
952 assert_eq!((1.div(&s)).name().as_str(), "foo");
953
954 Ok(())
955 }
956
957 #[test]
958 #[cfg(feature = "checked_arithmetic")]
959 fn test_checked_div() {
960 let s = Series::new("foo".into(), [1i32, 0, 1]);
961 let out = s.checked_div(&s).unwrap();
962 assert_eq!(Vec::from(out.i32().unwrap()), &[Some(1), None, Some(1)]);
963 let out = s.checked_div_num(0).unwrap();
964 assert_eq!(Vec::from(out.i32().unwrap()), &[None, None, None]);
965
966 let s_f32 = Series::new("float32".into(), [1.0f32, 0.0, 1.0]);
967 let out = s_f32.checked_div(&s_f32).unwrap();
968 assert_eq!(
969 Vec::from(out.f32().unwrap()),
970 &[Some(1.0f32), None, Some(1.0f32)]
971 );
972 let out = s_f32.checked_div_num(0.0f32).unwrap();
973 assert_eq!(Vec::from(out.f32().unwrap()), &[None, None, None]);
974
975 let s_f64 = Series::new("float64".into(), [1.0f64, 0.0, 1.0]);
976 let out = s_f64.checked_div(&s_f64).unwrap();
977 assert_eq!(
978 Vec::from(out.f64().unwrap()),
979 &[Some(1.0f64), None, Some(1.0f64)]
980 );
981 let out = s_f64.checked_div_num(0.0f64).unwrap();
982 assert_eq!(Vec::from(out.f64().unwrap()), &[None, None, None]);
983 }
984}