Jackie-Jiang commented on code in PR #20059:
URL: https://github.com/apache/pinot/pull/20059#discussion_r4383723560


##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -642,21 +643,29 @@ private static int generatedArrayLength(long start, long 
end, long inc) {
   }
 
   /// Length of the `float` sequence `start, start + inc, ...` bounded by 
`end`, inclusive. The step count is computed
-  /// in `float` arithmetic, as the single-stage engine always did.
+  /// in double precision; the rounding tolerance is based on the ULPs of the 
original float inputs.
   private static int generatedArrayLength(float start, float end, float inc) {
     checkFloatingPointArguments(start, end, inc);
-    // TODO: decimal steps such as 0.1 are not exact in binary, so this can 
drop the end; see apache/pinot#19814
-    return checkGeneratedArrayLength((long) ((end - start) / inc + 1) - 1);
+    // Use double precision for the step count and compare its error with ULPs 
of the original float inputs.
+    double steps = (end - start) / inc;
+    return generatedFloatingArrayLength(steps, Math.ulp(start), Math.ulp(end), 
Math.ulp(inc), inc);
   }
 
   /// Length of the `double` sequence `start, start + inc, ...` bounded by 
`end`, inclusive.
   private static int generatedArrayLength(double start, double end, double 
inc) {
     checkFloatingPointArguments(start, end, inc);
-    // Adding 1 before truncating matches the length the single-stage engine 
always computed. It absorbs part of the
-    // rounding error of decimal steps, so 0.1 to 0.3 by 0.1 keeps its end.
-    // TODO: it does not absorb all of it, so 0.1 to 0.7 by 0.1 still drops 
its end; see apache/pinot#19814
-    // A span too wide for a long saturates the cast at Long.MAX_VALUE, which 
the length check below rejects.
-    return checkGeneratedArrayLength((long) ((end - start) / inc + 1) - 1);
+    return generatedFloatingArrayLength((end - start) / inc, Math.ulp(start), 
Math.ulp(end), Math.ulp(inc), inc);
+  }
+
+  /// Snaps a floating-point step count to an integer only when the distance 
is within the rounding error of the input
+  /// bounds and increments. Otherwise, truncates toward the last step that 
does not pass the end.
+  private static int generatedFloatingArrayLength(double steps, double 
startUlp, double endUlp, double incUlp,
+      double inc) {
+    double nearestSteps = Math.rint(steps);
+    double roundingError = 2 * (startUlp + endUlp + Math.abs(nearestSteps) * 
incUlp) / Math.abs(inc);
+    double fullSteps = Math.abs(steps - nearestSteps) <= roundingError ? 
nearestSteps : Math.floor(steps);

Review Comment:
   The tolerance can admit a genuinely between-step endpoint even when no 
rounding error occurs. For example, `generateFloatArray(1000000f, 1000000.75f, 
1f)` now returns `[1000000, 1000001]`, whereas the base returns only 
`[1000000]`. All inputs and the `0.75` step count are exactly representable, 
but the tolerance is about `0.250000238`, so it accepts the quarter-step 
overshoot. Likewise, `generateDoubleArray(1e15, 1e15 + 1.5, 1d)` now includes 
`1e15 + 2`, beyond the end. Please tighten the snapping criterion so large 
offset ULPs do not turn genuine fractional step counts into whole steps, and 
add regression cases for both types.



##########
pinot-common/src/main/java/org/apache/pinot/common/function/scalar/ArrayFunctions.java:
##########
@@ -642,21 +643,29 @@ private static int generatedArrayLength(long start, long 
end, long inc) {
   }
 
   /// Length of the `float` sequence `start, start + inc, ...` bounded by 
`end`, inclusive. The step count is computed
-  /// in `float` arithmetic, as the single-stage engine always did.
+  /// in double precision; the rounding tolerance is based on the ULPs of the 
original float inputs.
   private static int generatedArrayLength(float start, float end, float inc) {
     checkFloatingPointArguments(start, end, inc);
-    // TODO: decimal steps such as 0.1 are not exact in binary, so this can 
drop the end; see apache/pinot#19814
-    return checkGeneratedArrayLength((long) ((end - start) / inc + 1) - 1);
+    // Use double precision for the step count and compare its error with ULPs 
of the original float inputs.
+    double steps = (end - start) / inc;

Review Comment:
   Minor: this still computes both the subtraction and division in `float`; 
assignment to `double` only widens the completed result. To calculate in double 
precision as the new comments and PR description claim, promote an operand 
before subtraction, e.g. `((double) end - start) / inc`, or correct the 
documentation if retaining float arithmetic is intentional. The existing 
arithmetic behavior predates this PR; the mismatch is in the new documentation.



-- 
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