kumarUjjawal commented on code in PR #25985:
URL: https://github.com/apache/datafusion/pull/25985#discussion_r4172092359


##########
datafusion/expr-common/src/interval_arithmetic.rs:
##########
@@ -861,19 +863,9 @@ impl Interval {
         else if lhs_ref.contains(&zero_point)? == Self::TRUE
             && !dt.is_unsigned_integer()
         {
-            Ok(div_helper_lhs_zero_inclusive(
-                &dt,
-                lhs_ref,
-                rhs_ref,
-                &zero_point,
-            ))
+            Ok(div_helper_lhs_zero_inclusive(&dt, lhs_ref, rhs_ref, &zero))
         } else {
-            Ok(div_helper_zero_exclusive(
-                &dt,
-                lhs_ref,
-                rhs_ref,
-                &zero_point,
-            ))
+            Ok(div_helper_zero_exclusive(&dt, lhs_ref, rhs_ref, &zero))

Review Comment:
   When an integer divisor ends at zero, the helpers divide by that 0 endpoint, 
and div_bounds turns it into an unbounded side. The bounds stay safe but get 
much wider than needed. Since integer division by zero always fails at runtime, 
the divisor could be shrunk first ([-3,0] -> [-3,-1], [0,3] -> [1,3]) before 
choosing the sign case.
   
   Failure scenario: Int64 [-3,-1] / [-3,0] now gives [0, +inf) instead of [0, 
3], and [-6,6] / [-3,0] gives (-inf, +inf) instead of [-6, 6]. Filter 
statistics for `a / b > k` then cannot rule out any rows. Shrinking the divisor 
once in `div()` would fix the sign choice and keep the bounds tight, with no 
special cases in each helper.



##########
datafusion/expr-common/src/interval_arithmetic.rs:
##########
@@ -3674,6 +3668,55 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn test_div_zero_endpoints() -> Result<()> {
+        let cases = [
+            ((-3_i64, 0_i64), (-2_i64, -1_i64), (0_i64, 3_i64)),
+            ((-3, 0), (1, 2), (-3, 0)),
+            ((0, 3), (-2, -1), (-3, 0)),
+            ((0, 3), (1, 2), (0, 3)),
+        ];
+        for ((lo, hi), (rlo, rhi), (expected_lo, expected_hi)) in cases {
+            let lhs = Interval::make(Some(lo), Some(hi))?;
+            let rhs = Interval::make(Some(rlo), Some(rhi))?;
+            assert_eq!(
+                lhs.div(&rhs)?,
+                Interval::make(Some(expected_lo), Some(expected_hi))?,
+                "{lhs:?} / {rhs:?}",
+            );
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_div_small_integer_intervals_contain_runtime_results() -> 
Result<()> {

Review Comment:
   The new tests only use Int64, but the change also affects unsigned types. A 
UInt lhs of [0,0] now goes to the 'non-positive' match arms, and no test checks 
this.
   
   Failure scenario: UInt32 [0,0] / [0,5] used to give [0, +inf) and now gives 
[0, 0]. This is correct, but no test checks it, so a later change could break 
the unsigned path without any test failing. Please add a few UInt cases, for 
example [0,0]/[0,5] and [0,0]/[0,0], to test_div or the exhaustive loop.



##########
datafusion/expr-common/src/interval_arithmetic.rs:
##########
@@ -1693,11 +1685,13 @@ fn div_helper_zero_exclusive(
     dt: &DataType,
     lhs: &Interval,
     rhs: &Interval,
-    zero_point: &Interval,
+    zero: &ScalarValue,
 ) -> Interval {
+    // An interval ending at zero is non-positive. Compare against zero itself,

Review Comment:
   The new comment explains the old bug ('not the negative endpoint of the 
integer zero neighborhood [-1, 1]') instead of the current rule. It is also 
only in one of the two helpers that use the same comparison.
   
   Failure scenario: The comment talks about code that no longer exists, and 
div_helper_lhs_zero_inclusive uses the same `rhs.upper <= *zero` check with no 
comment. A short shared note, for example 'an interval whose upper bound is <= 
0 is treated as non-positive', in both places (or on `div()`) would be clearer.



##########
datafusion/expr-common/src/interval_arithmetic.rs:
##########
@@ -1640,10 +1632,10 @@ fn div_helper_lhs_zero_inclusive(
     dt: &DataType,
     lhs: &Interval,
     rhs: &Interval,
-    zero_point: &Interval,
+    zero: &ScalarValue,
 ) -> Interval {
     // With the following interval bounds, there is no possibility to create 
an invalid interval.
-    if rhs.upper <= zero_point.lower && !rhs.upper.is_null() {
+    if rhs.upper <= *zero && !rhs.upper.is_null() {

Review Comment:
   No exact-bound test covers the changed branch in 
div_helper_lhs_zero_inclusive (lhs contains zero, divisor ends at zero). The 
exhaustive test only checks that values are contained, so a loose result would 
still pass.
   
   Failure scenario: Before this PR, [-6,6] / [-3,0] went to the positive 
branch and built an inverted interval [2,-2]. Now it gives an unbounded result. 
No test pins this result, so the branch could later get wider, or wrongly move 
to another sign arm, without any test noticing. Please add exact cases such as 
[-6,6]/[-3,0] and [-6,6]/[0,3] to test_div_zero_endpoints.



##########
datafusion/expr-common/src/interval_arithmetic.rs:
##########
@@ -3674,6 +3668,55 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn test_div_zero_endpoints() -> Result<()> {

Review Comment:
   test_div_zero_endpoints is a separate test with its own loop, but test_div 
already has a table of (lhs, rhs, expected) cases checked with assert_eq for 
integers.
   
   Failure scenario: Two places now hold exact division expectations, so future 
div cases may go into either one. The four new tuples could just be rows in the 
test_div `cases` vec, which removes about 15 lines of duplicate loop and assert 
code.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to