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

viirya 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 80c0f1a48 Add divide dyn kernel which produces null for division by 
zero (#2764)
80c0f1a48 is described below

commit 80c0f1a480ee51bc05a2224108575dfad1be8deb
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Wed Sep 21 19:12:24 2022 -0700

    Add divide dyn kernel which produces null for division by zero (#2764)
    
    * Add divide_dyn_opt kernel
    
    * Add test
    
    * Fix clippy
    
    * Rename function
---
 arrow/src/compute/kernels/arithmetic.rs | 102 ++++++++++++++++++++++++++++++++
 arrow/src/compute/kernels/arity.rs      |  69 ++++++++++++---------
 2 files changed, 143 insertions(+), 28 deletions(-)

diff --git a/arrow/src/compute/kernels/arithmetic.rs 
b/arrow/src/compute/kernels/arithmetic.rs
index b44cb8b94..d33827594 100644
--- a/arrow/src/compute/kernels/arithmetic.rs
+++ b/arrow/src/compute/kernels/arithmetic.rs
@@ -697,6 +697,39 @@ where
     )
 }
 
+#[cfg(feature = "dyn_arith_dict")]
+fn math_divide_safe_op_dict<K, T, F>(
+    left: &DictionaryArray<K>,
+    right: &DictionaryArray<K>,
+    op: F,
+) -> Result<ArrayRef>
+where
+    K: ArrowNumericType,
+    T: ArrowNumericType,
+    T::Native: One + Zero,
+    F: Fn(T::Native, T::Native) -> Option<T::Native>,
+{
+    let left = left.downcast_dict::<PrimitiveArray<T>>().unwrap();
+    let right = right.downcast_dict::<PrimitiveArray<T>>().unwrap();
+    let array: PrimitiveArray<T> = binary_opt::<_, _, _, T>(left, right, op)?;
+    Ok(Arc::new(array) as ArrayRef)
+}
+
+fn math_safe_divide_op<LT, RT, F>(
+    left: &PrimitiveArray<LT>,
+    right: &PrimitiveArray<RT>,
+    op: F,
+) -> Result<ArrayRef>
+where
+    LT: ArrowNumericType,
+    RT: ArrowNumericType,
+    RT::Native: One + Zero,
+    F: Fn(LT::Native, RT::Native) -> Option<LT::Native>,
+{
+    let array: PrimitiveArray<LT> = binary_opt::<_, _, _, LT>(left, right, 
op)?;
+    Ok(Arc::new(array) as ArrayRef)
+}
+
 /// Perform `left + right` operation on two arrays. If either left or right 
value is null
 /// then the result is also null.
 ///
@@ -1406,6 +1439,51 @@ pub fn divide_dyn_checked(left: &dyn Array, right: &dyn 
Array) -> Result<ArrayRe
     }
 }
 
+/// 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, the operation value will be replaced with 
null in the
+/// result.
+///
+/// Unlike `divide_dyn` or `divide_dyn_checked`, division by zero will get a 
null value instead
+/// returning an `Err`, this also doesn't check overflowing, overflowing will 
just wrap
+/// the result around.
+pub fn divide_dyn_opt(left: &dyn Array, right: &dyn Array) -> Result<ArrayRef> 
{
+    match left.data_type() {
+        DataType::Dictionary(_, _) => {
+            typed_dict_math_op!(
+                left,
+                right,
+                |a, b| {
+                    if b.is_zero() {
+                        None
+                    } else {
+                        Some(a.div_wrapping(b))
+                    }
+                },
+                math_divide_safe_op_dict
+            )
+        }
+        _ => {
+            downcast_primitive_array!(
+                (left, right) => {
+                    math_safe_divide_op(left, right, |a, b| {
+                        if b.is_zero() {
+                            None
+                        } else {
+                            Some(a.div_wrapping(b))
+                        }
+                    })
+                }
+                _ => Err(ArrowError::CastError(format!(
+                    "Unsupported data type {}, {}",
+                    left.data_type(), right.data_type()
+                )))
+            )
+        }
+    }
+}
+
 /// Perform `left / right` operation on two arrays without checking for 
division by zero.
 /// For floating point types, the result of dividing by zero follows normal 
floating point
 /// rules. For other numeric types, dividing by zero will panic,
@@ -2752,4 +2830,28 @@ mod tests {
         let overflow = divide_dyn_checked(&a, &b);
         overflow.expect_err("overflow should be detected");
     }
+
+    #[test]
+    #[cfg(feature = "dyn_arith_dict")]
+    fn test_div_dyn_opt_overflow_division_by_zero() {
+        let a = Int32Array::from(vec![i32::MIN]);
+        let b = Int32Array::from(vec![0]);
+
+        let division_by_zero = divide_dyn_opt(&a, &b);
+        let expected = Arc::new(Int32Array::from(vec![None])) as ArrayRef;
+        assert_eq!(&expected, &division_by_zero.unwrap());
+
+        let mut builder =
+            PrimitiveDictionaryBuilder::<Int8Type, 
Int32Type>::with_capacity(1, 1);
+        builder.append(i32::MIN).unwrap();
+        let a = builder.finish();
+
+        let mut builder =
+            PrimitiveDictionaryBuilder::<Int8Type, 
Int32Type>::with_capacity(1, 1);
+        builder.append(0).unwrap();
+        let b = builder.finish();
+
+        let division_by_zero = divide_dyn_opt(&a, &b);
+        assert_eq!(&expected, &division_by_zero.unwrap());
+    }
 }
diff --git a/arrow/src/compute/kernels/arity.rs 
b/arrow/src/compute/kernels/arity.rs
index bf1028968..5f875e6dd 100644
--- a/arrow/src/compute/kernels/arity.rs
+++ b/arrow/src/compute/kernels/arity.rs
@@ -357,6 +357,26 @@ where
     Ok(unsafe { build_primitive_array(len, buffer.into(), 0, None) })
 }
 
+#[inline(never)]
+fn try_binary_opt_no_nulls<A: ArrayAccessor, B: ArrayAccessor, F, O>(
+    len: usize,
+    a: A,
+    b: B,
+    op: F,
+) -> Result<PrimitiveArray<O>>
+where
+    O: ArrowPrimitiveType,
+    F: Fn(A::Item, B::Item) -> Option<O::Native>,
+{
+    let mut buffer = Vec::with_capacity(10);
+    for idx in 0..len {
+        unsafe {
+            buffer.push(op(a.value_unchecked(idx), b.value_unchecked(idx)));
+        };
+    }
+    Ok(buffer.iter().collect())
+}
+
 /// Applies the provided binary operation across `a` and `b`, collecting the 
optional results
 /// into a [`PrimitiveArray`]. If any index is null in either `a` or `b`, the 
corresponding
 /// index in the result will also be null. The binary operation could return 
`None` which
@@ -367,16 +387,14 @@ where
 /// # Error
 ///
 /// This function gives error if the arrays have different lengths
-pub(crate) fn binary_opt<A, B, F, O>(
-    a: &PrimitiveArray<A>,
-    b: &PrimitiveArray<B>,
+pub(crate) fn binary_opt<A: ArrayAccessor + Array, B: ArrayAccessor + Array, 
F, O>(
+    a: A,
+    b: B,
     op: F,
 ) -> Result<PrimitiveArray<O>>
 where
-    A: ArrowPrimitiveType,
-    B: ArrowPrimitiveType,
     O: ArrowPrimitiveType,
-    F: Fn(A::Native, B::Native) -> Option<O::Native>,
+    F: Fn(A::Item, B::Item) -> Option<O::Native>,
 {
     if a.len() != b.len() {
         return Err(ArrowError::ComputeError(
@@ -389,29 +407,24 @@ where
     }
 
     if a.null_count() == 0 && b.null_count() == 0 {
-        Ok(a.values()
-            .iter()
-            .zip(b.values().iter())
-            .map(|(a, b)| op(*a, *b))
-            .collect())
-    } else {
-        let iter_a = ArrayIter::new(a);
-        let iter_b = ArrayIter::new(b);
-
-        let values =
-            iter_a
-                .into_iter()
-                .zip(iter_b.into_iter())
-                .map(|(item_a, item_b)| {
-                    if let (Some(a), Some(b)) = (item_a, item_b) {
-                        op(a, b)
-                    } else {
-                        None
-                    }
-                });
-
-        Ok(values.collect())
+        return try_binary_opt_no_nulls(a.len(), a, b, op);
     }
+
+    let iter_a = ArrayIter::new(a);
+    let iter_b = ArrayIter::new(b);
+
+    let values = iter_a
+        .into_iter()
+        .zip(iter_b.into_iter())
+        .map(|(item_a, item_b)| {
+            if let (Some(a), Some(b)) = (item_a, item_b) {
+                op(a, b)
+            } else {
+                None
+            }
+        });
+
+    Ok(values.collect())
 }
 
 #[cfg(test)]

Reply via email to