polars_core/series/arithmetic/
borrowed.rs

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        // SAFETY:
58        // There will be UB if a ChunkedArray is alive with the wrong datatype.
59        // we now only create the potentially wrong dtype for a short time.
60        // Note that the physical type correctness is checked!
61        // The ChunkedArray with the wrong dtype is dropped after this operation
62        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        // SAFETY:
75        // see subtract
76        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        // SAFETY:
89        // see subtract
90        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        // SAFETY:
103        // see subtract
104        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        // SAFETY:
117        // see subtract
118        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        /// Checked integer division. Computes self / rhs, returning None if rhs == 0 or the division results in overflow.
156        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        /// Checked integer division. Computes self / rhs, returning None if rhs == 0 or the division results in overflow.
169        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            // SAFETY:
189            // There will be UB if a ChunkedArray is alive with the wrong datatype.
190            // we now only create the potentially wrong dtype for a short time.
191            // Note that the physical type correctness is checked!
192            // The ChunkedArray with the wrong dtype is dropped after this operation
193            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            // SAFETY:
208            // see check_div for chunkedarray<T>
209            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            // SAFETY:
229            // see check_div for chunkedarray<T>
230            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            // SAFETY:
250            // see check_div
251            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    // Correct the list and array types
364    //
365    // This also casts Lists <-> Array.
366    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
388// Handle (Date | Datetime) +/- (Duration) | (Duration) +/- (Date | Datetime) | (Duration) +-
389// (Duration)
390// Time arithmetic is only implemented on the date / datetime so ensure that's on left
391
392fn 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        // make sure to return Some here, so we don't cast to supertype.
412        (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        // swap the order
428        (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            // Expects lengths to be equal.
474            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        // broadcasting
490        (1, _) | (_, 1) => Ok(()),
491        // equal
492        (a, b) if a == b => Ok(()),
493        // unequal
494        (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    /// ```
554    /// # use polars_core::prelude::*;
555    /// let s: Series = [1, 2, 3].iter().collect();
556    /// let out = (&s * &s).unwrap();
557    /// ```
558    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            // temporal lh
566            (Duration(_), _) | (Date, _) | (Datetime(_, _), _) | (Time, _) => self.multiply(rhs),
567            // temporal rhs
568            (_, Date) | (_, Datetime(_, _)) | (_, Time) => {
569                polars_bail!(opq = mul, self.dtype(), rhs.dtype())
570            },
571            (_, Duration(_)) => {
572                // swap order
573                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    /// ```
595    /// # use polars_core::prelude::*;
596    /// let s: Series = [1, 2, 3].iter().collect();
597    /// let out = (&s / &s).unwrap();
598    /// ```
599    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    /// ```
632    /// # use polars_core::prelude::*;
633    /// let s: Series = [1, 2, 3].iter().collect();
634    /// let out = (&s / &s).unwrap();
635    /// ```
636    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
658// Series +-/* numbers instead of Series
659
660fn 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
757// TODO: remove this, temporary band-aid.
758impl 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
827/// We cannot override the left hand side behaviour. So we create a trait LhsNumOps.
828/// This allows for 1.add(&Series)
829///
830impl<T: PolarsNumericType> ChunkedArray<T> {
831    /// Apply lhs - self
832    #[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    /// Apply lhs / self
839    #[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    /// Apply lhs % self
846    #[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        // order doesn't matter, dispatch to rhs + lhs
871        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        // order doesn't matter, dispatch to rhs * lhs
893        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        // Series +-/* Series
915        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        // Series +-/* Number
933        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        // Lhs operations
951        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}