You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by al...@apache.org on 2021/06/04 15:17:20 UTC

[arrow-rs] branch master updated: Add (simd) modulus op (#317)

This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/master by this push:
     new 0a16574  Add (simd) modulus op (#317)
0a16574 is described below

commit 0a165748c924b3752cc00ff86e56917427985872
Author: Gang Liao <ga...@umd.edu>
AuthorDate: Fri Jun 4 08:17:07 2021 -0700

    Add (simd) modulus op (#317)
    
    * Add (simd) modulus op
    
    * fix typo
    
    * fix feature = "simd"
    
    * revert ModulusByZero
---
 arrow/src/compute/kernels/arithmetic.rs | 532 +++++++++++++++++++++++++++++++-
 arrow/src/datatypes/numeric.rs          |   8 +-
 2 files changed, 536 insertions(+), 4 deletions(-)

diff --git a/arrow/src/compute/kernels/arithmetic.rs b/arrow/src/compute/kernels/arithmetic.rs
index 0c46d61..50c06b0 100644
--- a/arrow/src/compute/kernels/arithmetic.rs
+++ b/arrow/src/compute/kernels/arithmetic.rs
@@ -22,7 +22,7 @@
 //! `RUSTFLAGS="-C target-feature=+avx2"` for example.  See the documentation
 //! [here](https://doc.rust-lang.org/stable/core/arch/) for more information.
 
-use std::ops::{Add, Div, Mul, Neg, Sub};
+use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
 
 use num::{One, Zero};
 
@@ -189,6 +189,74 @@ where
     Ok(PrimitiveArray::<T>::from(data))
 }
 
+/// Helper function to modulus two arrays.
+///
+/// # Errors
+///
+/// This function errors if:
+/// * the arrays have different lengths
+/// * a division by zero is found
+fn math_modulus<T>(
+    left: &PrimitiveArray<T>,
+    right: &PrimitiveArray<T>,
+) -> Result<PrimitiveArray<T>>
+where
+    T: ArrowNumericType,
+    T::Native: Rem<Output = T::Native> + Zero,
+{
+    if left.len() != right.len() {
+        return Err(ArrowError::ComputeError(
+            "Cannot perform math operation on arrays of different length".to_string(),
+        ));
+    }
+
+    let null_bit_buffer =
+        combine_option_bitmap(left.data_ref(), right.data_ref(), left.len())?;
+
+    let buffer = if let Some(b) = &null_bit_buffer {
+        let values = left.values().iter().zip(right.values()).enumerate().map(
+            |(i, (left, right))| {
+                let is_valid = unsafe { bit_util::get_bit_raw(b.as_ptr(), i) };
+                if is_valid {
+                    if right.is_zero() {
+                        Err(ArrowError::DivideByZero)
+                    } else {
+                        Ok(*left % *right)
+                    }
+                } else {
+                    Ok(T::default_value())
+                }
+            },
+        );
+        unsafe { Buffer::try_from_trusted_len_iter(values) }
+    } else {
+        // no value is null
+        let values = left
+            .values()
+            .iter()
+            .zip(right.values())
+            .map(|(left, right)| {
+                if right.is_zero() {
+                    Err(ArrowError::DivideByZero)
+                } else {
+                    Ok(*left % *right)
+                }
+            });
+        unsafe { Buffer::try_from_trusted_len_iter(values) }
+    }?;
+
+    let data = ArrayData::new(
+        T::DATA_TYPE,
+        left.len(),
+        None,
+        null_bit_buffer,
+        0,
+        vec![buffer],
+        vec![],
+    );
+    Ok(PrimitiveArray::<T>::from(data))
+}
+
 /// Helper function to divide two arrays.
 ///
 /// # Errors
@@ -257,6 +325,34 @@ where
     Ok(PrimitiveArray::<T>::from(data))
 }
 
+/// Scalar-modulo version of `math_modulus`.
+fn math_modulus_scalar<T>(
+    array: &PrimitiveArray<T>,
+    modulo: T::Native,
+) -> Result<PrimitiveArray<T>>
+where
+    T: ArrowNumericType,
+    T::Native: Rem<Output = T::Native> + Zero,
+{
+    if modulo.is_zero() {
+        return Err(ArrowError::DivideByZero);
+    }
+
+    let values = array.values().iter().map(|value| *value % modulo);
+    let buffer = unsafe { Buffer::from_trusted_len_iter(values) };
+
+    let data = ArrayData::new(
+        T::DATA_TYPE,
+        array.len(),
+        None,
+        array.data_ref().null_buffer().cloned(),
+        0,
+        vec![buffer],
+        vec![],
+    );
+    Ok(PrimitiveArray::<T>::from(data))
+}
+
 /// Scalar-divisor version of `math_divide`.
 fn math_divide_scalar<T>(
     array: &PrimitiveArray<T>,
@@ -348,6 +444,40 @@ where
     Ok(PrimitiveArray::<T>::from(data))
 }
 
+/// SIMD vectorized implementation of `left % right`.
+/// If any of the lanes marked as valid in `valid_mask` are `0` then an `ArrowError::DivideByZero`
+/// is returned. The contents of no-valid lanes are undefined.
+#[cfg(feature = "simd")]
+#[inline]
+fn simd_checked_modulus<T: ArrowNumericType>(
+    valid_mask: Option<u64>,
+    left: T::Simd,
+    right: T::Simd,
+) -> Result<T::Simd>
+where
+    T::Native: One + Zero,
+{
+    let zero = T::init(T::Native::zero());
+    let one = T::init(T::Native::one());
+
+    let right_no_invalid_zeros = match valid_mask {
+        Some(mask) => {
+            let simd_mask = T::mask_from_u64(mask);
+            // select `1` for invalid lanes, which will be a no-op during division later
+            T::mask_select(simd_mask, right, one)
+        }
+        None => right,
+    };
+
+    let zero_mask = T::eq(right_no_invalid_zeros, zero);
+
+    if T::mask_any(zero_mask) {
+        Err(ArrowError::DivideByZero)
+    } else {
+        Ok(T::bin_op(left, right_no_invalid_zeros, |a, b| a % b))
+    }
+}
+
 /// SIMD vectorized implementation of `left / right`.
 /// If any of the lanes marked as valid in `valid_mask` are `0` then an `ArrowError::DivideByZero`
 /// is returned. The contents of no-valid lanes are undefined.
@@ -382,6 +512,40 @@ where
     }
 }
 
+/// Scalar implementation of `left % right` for the remainder elements after complete chunks have been processed using SIMD.
+/// If any of the values marked as valid in `valid_mask` are `0` then an `ArrowError::DivideByZero` is returned.
+#[cfg(feature = "simd")]
+#[inline]
+fn simd_checked_modulus_remainder<T: ArrowNumericType>(
+    valid_mask: Option<u64>,
+    left_chunks: ChunksExact<T::Native>,
+    right_chunks: ChunksExact<T::Native>,
+    result_chunks: ChunksExactMut<T::Native>,
+) -> Result<()>
+where
+    T::Native: Zero + Rem<Output = T::Native>,
+{
+    let result_remainder = result_chunks.into_remainder();
+    let left_remainder = left_chunks.remainder();
+    let right_remainder = right_chunks.remainder();
+
+    result_remainder
+        .iter_mut()
+        .zip(left_remainder.iter().zip(right_remainder.iter()))
+        .enumerate()
+        .try_for_each(|(i, (result_scalar, (left_scalar, right_scalar)))| {
+            if valid_mask.map(|mask| mask & (1 << i) != 0).unwrap_or(true) {
+                if *right_scalar == T::Native::zero() {
+                    return Err(ArrowError::DivideByZero);
+                }
+                *result_scalar = *left_scalar % *right_scalar;
+            }
+            Ok(())
+        })?;
+
+    Ok(())
+}
+
 /// Scalar implementation of `left / right` for the remainder elements after complete chunks have been processed using SIMD.
 /// If any of the values marked as valid in `valid_mask` are `0` then an `ArrowError::DivideByZero` is returned.
 #[cfg(feature = "simd")]
@@ -416,6 +580,34 @@ where
     Ok(())
 }
 
+/// Scalar-modulo version of `simd_checked_modulus_remainder`.
+#[cfg(feature = "simd")]
+#[inline]
+fn simd_checked_modulus_scalar_remainder<T: ArrowNumericType>(
+    array_chunks: ChunksExact<T::Native>,
+    modulo: T::Native,
+    result_chunks: ChunksExactMut<T::Native>,
+) -> Result<()>
+where
+    T::Native: Zero + Rem<Output = T::Native>,
+{
+    if modulo.is_zero() {
+        return Err(ArrowError::DivideByZero);
+    }
+
+    let result_remainder = result_chunks.into_remainder();
+    let array_remainder = array_chunks.remainder();
+
+    result_remainder
+        .iter_mut()
+        .zip(array_remainder.iter())
+        .for_each(|(result_scalar, array_scalar)| {
+            *result_scalar = *array_scalar % modulo;
+        });
+
+    Ok(())
+}
+
 /// Scalar-divisor version of `simd_checked_divide_remainder`.
 #[cfg(feature = "simd")]
 #[inline]
@@ -444,6 +636,126 @@ where
     Ok(())
 }
 
+/// SIMD vectorized version of `modulus`.
+///
+/// The modulus kernels need their own implementation as there is a need to handle situations
+/// where a modulus by `0` occurs.  This is complicated by `NULL` slots and padding.
+#[cfg(feature = "simd")]
+fn simd_modulus<T>(
+    left: &PrimitiveArray<T>,
+    right: &PrimitiveArray<T>,
+) -> Result<PrimitiveArray<T>>
+where
+    T: ArrowNumericType,
+    T::Native: One + Zero + Rem<Output = T::Native>,
+{
+    if left.len() != right.len() {
+        return Err(ArrowError::ComputeError(
+            "Cannot perform math operation on arrays of different length".to_string(),
+        ));
+    }
+
+    // Create the combined `Bitmap`
+    let null_bit_buffer =
+        combine_option_bitmap(left.data_ref(), right.data_ref(), left.len())?;
+
+    let lanes = T::lanes();
+    let buffer_size = left.len() * std::mem::size_of::<T::Native>();
+    let mut result = MutableBuffer::new(buffer_size).with_bitset(buffer_size, false);
+
+    match &null_bit_buffer {
+        Some(b) => {
+            // combine_option_bitmap returns a slice or new buffer starting at 0
+            let valid_chunks = b.bit_chunks(0, left.len());
+
+            // process data in chunks of 64 elements since we also get 64 bits of validity information at a time
+            let mut result_chunks = result.typed_data_mut().chunks_exact_mut(64);
+            let mut left_chunks = left.values().chunks_exact(64);
+            let mut right_chunks = right.values().chunks_exact(64);
+
+            valid_chunks
+                .iter()
+                .zip(
+                    result_chunks
+                        .borrow_mut()
+                        .zip(left_chunks.borrow_mut().zip(right_chunks.borrow_mut())),
+                )
+                .try_for_each(
+                    |(mut mask, (result_slice, (left_slice, right_slice)))| {
+                        // split chunks further into slices corresponding to the vector length
+                        // the compiler is able to unroll this inner loop and remove bounds checks
+                        // since the outer chunk size (64) is always a multiple of the number of lanes
+                        result_slice
+                            .chunks_exact_mut(lanes)
+                            .zip(left_slice.chunks_exact(lanes).zip(right_slice.chunks_exact(lanes)))
+                            .try_for_each(|(result_slice, (left_slice, right_slice))| -> Result<()> {
+                                let simd_left = T::load(left_slice);
+                                let simd_right = T::load(right_slice);
+
+                                let simd_result = simd_checked_modulus::<T>(Some(mask), simd_left, simd_right)?;
+
+                                T::write(simd_result, result_slice);
+
+                                // skip the shift and avoid overflow for u8 type, which uses 64 lanes.
+                                mask >>= T::lanes() % 64;
+
+                                Ok(())
+                            })
+                    },
+                )?;
+
+            let valid_remainder = valid_chunks.remainder_bits();
+
+            simd_checked_modulus_remainder::<T>(
+                Some(valid_remainder),
+                left_chunks,
+                right_chunks,
+                result_chunks,
+            )?;
+        }
+        None => {
+            let mut result_chunks = result.typed_data_mut().chunks_exact_mut(lanes);
+            let mut left_chunks = left.values().chunks_exact(lanes);
+            let mut right_chunks = right.values().chunks_exact(lanes);
+
+            result_chunks
+                .borrow_mut()
+                .zip(left_chunks.borrow_mut().zip(right_chunks.borrow_mut()))
+                .try_for_each(
+                    |(result_slice, (left_slice, right_slice))| -> Result<()> {
+                        let simd_left = T::load(left_slice);
+                        let simd_right = T::load(right_slice);
+
+                        let simd_result =
+                            simd_checked_modulus::<T>(None, simd_left, simd_right)?;
+
+                        T::write(simd_result, result_slice);
+
+                        Ok(())
+                    },
+                )?;
+
+            simd_checked_modulus_remainder::<T>(
+                None,
+                left_chunks,
+                right_chunks,
+                result_chunks,
+            )?;
+        }
+    }
+
+    let data = ArrayData::new(
+        T::DATA_TYPE,
+        left.len(),
+        None,
+        null_bit_buffer,
+        0,
+        vec![result.into()],
+        vec![],
+    );
+    Ok(PrimitiveArray::<T>::from(data))
+}
+
 /// SIMD vectorized version of `divide`.
 ///
 /// The divide kernels need their own implementation as there is a need to handle situations
@@ -564,6 +876,52 @@ where
     Ok(PrimitiveArray::<T>::from(data))
 }
 
+/// SIMD vectorized version of `modulus_scalar`.
+#[cfg(feature = "simd")]
+fn simd_modulus_scalar<T>(
+    array: &PrimitiveArray<T>,
+    modulo: T::Native,
+) -> Result<PrimitiveArray<T>>
+where
+    T: ArrowNumericType,
+    T::Native: One + Zero + Rem<Output = T::Native>,
+{
+    if modulo.is_zero() {
+        return Err(ArrowError::DivideByZero);
+    }
+
+    let lanes = T::lanes();
+    let buffer_size = array.len() * std::mem::size_of::<T::Native>();
+    let mut result = MutableBuffer::new(buffer_size).with_bitset(buffer_size, false);
+
+    let mut result_chunks = result.typed_data_mut().chunks_exact_mut(lanes);
+    let mut array_chunks = array.values().chunks_exact(lanes);
+
+    result_chunks
+        .borrow_mut()
+        .zip(array_chunks.borrow_mut())
+        .for_each(|(result_slice, array_slice)| {
+            let simd_left = T::load(array_slice);
+            let simd_right = T::init(modulo);
+
+            let simd_result = T::bin_op(simd_left, simd_right, |a, b| a % b);
+            T::write(simd_result, result_slice);
+        });
+
+    simd_checked_modulus_scalar_remainder::<T>(array_chunks, modulo, result_chunks)?;
+
+    let data = ArrayData::new(
+        T::DATA_TYPE,
+        array.len(),
+        None,
+        array.data_ref().null_buffer().cloned(),
+        0,
+        vec![result.into()],
+        vec![],
+    );
+    Ok(PrimitiveArray::<T>::from(data))
+}
+
 /// SIMD vectorized version of `divide_scalar`.
 #[cfg(feature = "simd")]
 fn simd_divide_scalar<T>(
@@ -696,6 +1054,7 @@ where
         + Sub<Output = T::Native>
         + Mul<Output = T::Native>
         + Div<Output = T::Native>
+        + Rem<Output = T::Native>
         + Zero,
 {
     #[cfg(feature = "simd")]
@@ -704,6 +1063,29 @@ where
     return math_op(left, right, |a, b| a * b);
 }
 
+/// Perform `left % right` operation on two arrays. If either left or right value is null
+/// then the result is also null. If any right hand value is zero then the result of this
+/// operation will be `Err(ArrowError::DivideByZero)`.
+pub fn modulus<T>(
+    left: &PrimitiveArray<T>,
+    right: &PrimitiveArray<T>,
+) -> Result<PrimitiveArray<T>>
+where
+    T: datatypes::ArrowNumericType,
+    T::Native: Add<Output = T::Native>
+        + Sub<Output = T::Native>
+        + Mul<Output = T::Native>
+        + Div<Output = T::Native>
+        + Rem<Output = T::Native>
+        + Zero
+        + One,
+{
+    #[cfg(feature = "simd")]
+    return simd_modulus(&left, &right);
+    #[cfg(not(feature = "simd"))]
+    return math_modulus(&left, &right);
+}
+
 /// Perform `left / right` operation on two arrays. If either left or right value is null
 /// then the result is also null. If any right hand value is zero then the result of this
 /// operation will be `Err(ArrowError::DivideByZero)`.
@@ -717,6 +1099,7 @@ where
         + Sub<Output = T::Native>
         + Mul<Output = T::Native>
         + Div<Output = T::Native>
+        + Rem<Output = T::Native>
         + Zero
         + One,
 {
@@ -726,6 +1109,29 @@ where
     return math_divide(&left, &right);
 }
 
+/// Modulus every value in an array by a scalar. If any value in the array is null then the
+/// result is also null. If the scalar is zero then the result of this operation will be
+/// `Err(ArrowError::DivideByZero)`.
+pub fn modulus_scalar<T>(
+    array: &PrimitiveArray<T>,
+    modulo: T::Native,
+) -> Result<PrimitiveArray<T>>
+where
+    T: datatypes::ArrowNumericType,
+    T::Native: Add<Output = T::Native>
+        + Sub<Output = T::Native>
+        + Mul<Output = T::Native>
+        + Div<Output = T::Native>
+        + Rem<Output = T::Native>
+        + Zero
+        + One,
+{
+    #[cfg(feature = "simd")]
+    return simd_modulus_scalar(&array, modulo);
+    #[cfg(not(feature = "simd"))]
+    return math_modulus_scalar(&array, modulo);
+}
+
 /// Divide every value in an array by a scalar. If any value in the array is null then the
 /// result is also null. If the scalar is zero then the result of this operation will be
 /// `Err(ArrowError::DivideByZero)`.
@@ -739,6 +1145,7 @@ where
         + Sub<Output = T::Native>
         + Mul<Output = T::Native>
         + Div<Output = T::Native>
+        + Rem<Output = T::Native>
         + Zero
         + One,
 {
@@ -836,6 +1243,18 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus() {
+        let a = Int32Array::from(vec![15, 15, 8, 1, 9]);
+        let b = Int32Array::from(vec![5, 6, 8, 9, 1]);
+        let c = modulus(&a, &b).unwrap();
+        assert_eq!(0, c.value(0));
+        assert_eq!(3, c.value(1));
+        assert_eq!(0, c.value(2));
+        assert_eq!(1, c.value(3));
+        assert_eq!(0, c.value(4));
+    }
+
+    #[test]
     fn test_primitive_array_divide_scalar() {
         let a = Int32Array::from(vec![15, 14, 9, 8, 1]);
         let b = 3;
@@ -845,6 +1264,15 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus_scalar() {
+        let a = Int32Array::from(vec![15, 14, 9, 8, 1]);
+        let b = 3;
+        let c = modulus_scalar(&a, b).unwrap();
+        let expected = Int32Array::from(vec![0, 2, 0, 2, 1]);
+        assert_eq!(c, expected);
+    }
+
+    #[test]
     fn test_primitive_array_divide_sliced() {
         let a = Int32Array::from(vec![0, 0, 0, 15, 15, 8, 1, 9, 0]);
         let b = Int32Array::from(vec![0, 0, 0, 5, 6, 8, 9, 1, 0]);
@@ -863,6 +1291,24 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus_sliced() {
+        let a = Int32Array::from(vec![0, 0, 0, 15, 15, 8, 1, 9, 0]);
+        let b = Int32Array::from(vec![0, 0, 0, 5, 6, 8, 9, 1, 0]);
+        let a = a.slice(3, 5);
+        let b = b.slice(3, 5);
+        let a = a.as_any().downcast_ref::<Int32Array>().unwrap();
+        let b = b.as_any().downcast_ref::<Int32Array>().unwrap();
+
+        let c = modulus(&a, &b).unwrap();
+        assert_eq!(5, c.len());
+        assert_eq!(0, c.value(0));
+        assert_eq!(3, c.value(1));
+        assert_eq!(0, c.value(2));
+        assert_eq!(1, c.value(3));
+        assert_eq!(0, c.value(4));
+    }
+
+    #[test]
     fn test_primitive_array_divide_with_nulls() {
         let a = Int32Array::from(vec![Some(15), None, Some(8), Some(1), Some(9), None]);
         let b = Int32Array::from(vec![Some(5), Some(6), Some(8), Some(9), None, None]);
@@ -876,6 +1322,19 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus_with_nulls() {
+        let a = Int32Array::from(vec![Some(15), None, Some(8), Some(1), Some(9), None]);
+        let b = Int32Array::from(vec![Some(5), Some(6), Some(8), Some(9), None, None]);
+        let c = modulus(&a, &b).unwrap();
+        assert_eq!(0, c.value(0));
+        assert_eq!(true, c.is_null(1));
+        assert_eq!(0, c.value(2));
+        assert_eq!(1, c.value(3));
+        assert_eq!(true, c.is_null(4));
+        assert_eq!(true, c.is_null(5));
+    }
+
+    #[test]
     fn test_primitive_array_divide_scalar_with_nulls() {
         let a = Int32Array::from(vec![Some(15), None, Some(8), Some(1), Some(9), None]);
         let b = 3;
@@ -886,6 +1345,16 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus_scalar_with_nulls() {
+        let a = Int32Array::from(vec![Some(15), None, Some(8), Some(1), Some(9), None]);
+        let b = 3;
+        let c = modulus_scalar(&a, b).unwrap();
+        let expected =
+            Int32Array::from(vec![Some(0), None, Some(2), Some(1), Some(0), None]);
+        assert_eq!(c, expected);
+    }
+
+    #[test]
     fn test_primitive_array_divide_with_nulls_sliced() {
         let a = Int32Array::from(vec![
             None,
@@ -939,6 +1408,59 @@ mod tests {
     }
 
     #[test]
+    fn test_primitive_array_modulus_with_nulls_sliced() {
+        let a = Int32Array::from(vec![
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            Some(15),
+            None,
+            Some(8),
+            Some(1),
+            Some(9),
+            None,
+            None,
+        ]);
+        let b = Int32Array::from(vec![
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            None,
+            Some(5),
+            Some(6),
+            Some(8),
+            Some(9),
+            None,
+            None,
+            None,
+        ]);
+
+        let a = a.slice(8, 6);
+        let a = a.as_any().downcast_ref::<Int32Array>().unwrap();
+
+        let b = b.slice(8, 6);
+        let b = b.as_any().downcast_ref::<Int32Array>().unwrap();
+
+        let c = modulus(&a, &b).unwrap();
+        assert_eq!(6, c.len());
+        assert_eq!(0, c.value(0));
+        assert_eq!(true, c.is_null(1));
+        assert_eq!(0, c.value(2));
+        assert_eq!(1, c.value(3));
+        assert_eq!(true, c.is_null(4));
+        assert_eq!(true, c.is_null(5));
+    }
+
+    #[test]
     #[should_panic(expected = "DivideByZero")]
     fn test_primitive_array_divide_by_zero() {
         let a = Int32Array::from(vec![15]);
@@ -947,6 +1469,14 @@ mod tests {
     }
 
     #[test]
+    #[should_panic(expected = "DivideByZero")]
+    fn test_primitive_array_modulus_by_zero() {
+        let a = Int32Array::from(vec![15]);
+        let b = Int32Array::from(vec![0]);
+        modulus(&a, &b).unwrap();
+    }
+
+    #[test]
     fn test_primitive_array_divide_f64() {
         let a = Float64Array::from(vec![15.0, 15.0, 8.0]);
         let b = Float64Array::from(vec![5.0, 6.0, 8.0]);
diff --git a/arrow/src/datatypes/numeric.rs b/arrow/src/datatypes/numeric.rs
index 61f8d88..39c6732 100644
--- a/arrow/src/datatypes/numeric.rs
+++ b/arrow/src/datatypes/numeric.rs
@@ -15,12 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use super::*;
 #[cfg(feature = "simd")]
 use packed_simd::*;
 #[cfg(feature = "simd")]
-use std::ops::{Add, BitAnd, BitAndAssign, BitOr, BitOrAssign, Div, Mul, Neg, Not, Sub};
-
-use super::*;
+use std::ops::{
+    Add, BitAnd, BitAndAssign, BitOr, BitOrAssign, Div, Mul, Neg, Not, Rem, Sub,
+};
 
 /// A subtype of primitive type that represents numeric values.
 ///
@@ -32,6 +33,7 @@ where
         + Sub<Output = Self::Simd>
         + Mul<Output = Self::Simd>
         + Div<Output = Self::Simd>
+        + Rem<Output = Self::Simd>
         + Copy,
     Self::SimdMask: BitAnd<Output = Self::SimdMask>
         + BitOr<Output = Self::SimdMask>