Skip to main content

polars_core/series/arithmetic/
borrowed.rs

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        // SAFETY:
60        // There will be UB if a ChunkedArray is alive with the wrong datatype.
61        // we now only create the potentially wrong dtype for a short time.
62        // Note that the physical type correctness is checked!
63        // The ChunkedArray with the wrong dtype is dropped after this operation
64        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        // SAFETY:
77        // see subtract
78        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        // SAFETY:
91        // see subtract
92        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        // SAFETY:
105        // see subtract
106        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        // SAFETY:
119        // see subtract
120        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        /// Checked integer division. Computes self / rhs, returning None if rhs == 0 or the division results in overflow.
158        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        /// Checked integer division. Computes self / rhs, returning None if rhs == 0 or the division results in overflow.
171        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            // SAFETY:
191            // There will be UB if a ChunkedArray is alive with the wrong datatype.
192            // we now only create the potentially wrong dtype for a short time.
193            // Note that the physical type correctness is checked!
194            // The ChunkedArray with the wrong dtype is dropped after this operation
195            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            // SAFETY:
210            // see check_div for chunkedarray<T>
211            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            // SAFETY:
231            // see check_div for chunkedarray<T>
232            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            // SAFETY:
252            // see check_div
253            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    // Correct the list and array types
366    //
367    // This also casts Lists <-> Array.
368    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
390// Handle (Date | Datetime) +/- (Duration) | (Duration) +/- (Date | Datetime) | (Duration) +-
391// (Duration)
392// Time arithmetic is only implemented on the date / datetime so ensure that's on left
393
394fn 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        // make sure to return Some here, so we don't cast to supertype.
414        (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        // swap the order
430        (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            // Expects lengths to be equal.
469            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        // broadcasting
485        (1, _) | (_, 1) => Ok(()),
486        // equal
487        (a, b) if a == b => Ok(()),
488        // unequal
489        (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    /// ```
555    /// # use polars_core::prelude::*;
556    /// let s: Series = [1, 2, 3].iter().collect();
557    /// let out = (&s * &s).unwrap();
558    /// ```
559    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            // temporal lh
567            (Duration(_), _) | (Date, _) | (Datetime(_, _), _) | (Time, _) => self.multiply(rhs),
568            // temporal rhs
569            (_, Date) | (_, Datetime(_, _)) | (_, Time) => {
570                polars_bail!(opq = mul, self.dtype(), rhs.dtype())
571            },
572            (_, Duration(_)) => {
573                // swap order
574                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    /// ```
596    /// # use polars_core::prelude::*;
597    /// let s: Series = [1, 2, 3].iter().collect();
598    /// let out = (&s / &s).unwrap();
599    /// ```
600    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    /// ```
633    /// # use polars_core::prelude::*;
634    /// let s: Series = [1, 2, 3].iter().collect();
635    /// let out = (&s / &s).unwrap();
636    /// ```
637    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
659// Series +-/* numbers instead of Series
660
661fn 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
758// TODO: remove this, temporary band-aid.
759impl 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
828/// We cannot override the left hand side behaviour. So we create a trait LhsNumOps.
829/// This allows for 1.add(&Series)
830///
831impl<T: PolarsNumericType> ChunkedArray<T> {
832    /// Apply lhs - self
833    #[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    /// Apply lhs / self
840    #[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    /// Apply lhs % self
847    #[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        // order doesn't matter, dispatch to rhs + lhs
872        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        // order doesn't matter, dispatch to rhs * lhs
894        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        // Series +-/* Series
916        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        // Series +-/* Number
934        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        // Lhs operations
952        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}