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

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new f95b0015a6 GH-51194: [C++] Fix cumulative_max/min default start for 
floating-point types (#51203)
f95b0015a6 is described below

commit f95b0015a6e4850c3cba69c9e8ba1d384037b9e0
Author: Adarsh Mishra <[email protected]>
AuthorDate: Mon Sep 7 15:42:02 2026 +0530

    GH-51194: [C++] Fix cumulative_max/min default start for floating-point 
types (#51203)
    
    ## Rationale for this change
    
    `Identity<Max>` (the implicit default start of `cumulative_max`) used 
`std::numeric_limits<T>::min()`, which is the smallest **positive** value for 
floating-point types. Any input starting with a non-positive value therefore 
never replaced the start as the new maximum:
    
    ```python
    >>> import pyarrow as pa, pyarrow.compute as pc
    >>> pc.cumulative_max(pa.array([-2.5, 2.5])).to_pylist()
    [2.2250738585072014e-308, 2.5]   # expected [-2.5, 2.5]
    ```
    
    ## What changes are included in this PR?
    
    - `Identity<Max>` now uses `-infinity` for floating-point types (and 
half-float), and keeps `lowest()` for integer types. `-infinity` is the only 
value satisfying the documented identity property `Op(identity, x) = x for all 
x` — `lowest()` (most negative finite value) fails it for a leading `-inf` 
input.
    - `Identity<Min>` gets the mirror fix: `+infinity` for floating-point 
types. Previously `cumulative_min([inf, 1.0])` returned `[DBL_MAX, 1.0]` 
instead of `[inf, 1.0]`.
    
    Closes #51194.
    
    ## Are these changes tested?
    
    - Added `TestCumulative.NegativeValues` covering negative integers, 
negative floats, and `±Inf` inputs for both `cumulative_max` and 
`cumulative_min`.
    - Added a pyarrow regression test reproducing the issue 
(`test_cumulative_max_min_negative_default_start`).
    
    ## Are there any user-facing changes?
    
    Yes: `cumulative_max`/`cumulative_min` now return correct results for 
floating-point inputs whose first value is non-positive (for max) / 
non-negative or `inf` (for min). The documented behavior — 'the default start 
is the minimum/maximum value of input type' — is now actually honored.
    * GitHub Issue: #51194
    
    Authored-by: Adarsh <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 .../compute/kernels/base_arithmetic_internal.h     | 16 ++++++++++--
 .../compute/kernels/vector_cumulative_ops_test.cc  | 29 ++++++++++++++++++++++
 python/pyarrow/tests/test_compute.py               | 22 ++++++++++++++++
 3 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h 
b/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h
index b4840061ae..320c40373c 100644
--- a/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h
+++ b/cpp/src/arrow/compute/kernels/base_arithmetic_internal.h
@@ -759,7 +759,14 @@ template <>
 struct Identity<Max> {
   template <typename Value>
   static constexpr Value value() {
-    return std::numeric_limits<Value>::min();
+    // Note that `min()` returns the smallest positive value for
+    // floating-point types, and `lowest()` doesn't satisfy the identity
+    // property for -inf inputs, so use -infinity for those types.
+    if constexpr (std::is_floating_point_v<Value> || std::is_same_v<Float16, 
Value>) {
+      return -std::numeric_limits<Value>::infinity();
+    } else {
+      return std::numeric_limits<Value>::lowest();
+    }
   }
 };
 
@@ -767,7 +774,12 @@ template <>
 struct Identity<Min> {
   template <typename Value>
   static constexpr Value value() {
-    return std::numeric_limits<Value>::max();
+    // Mirror of Identity<Max>: use +infinity for floating-point types.
+    if constexpr (std::is_floating_point_v<Value> || std::is_same_v<Float16, 
Value>) {
+      return std::numeric_limits<Value>::infinity();
+    } else {
+      return std::numeric_limits<Value>::max();
+    }
   }
 };
 
diff --git a/cpp/src/arrow/compute/kernels/vector_cumulative_ops_test.cc 
b/cpp/src/arrow/compute/kernels/vector_cumulative_ops_test.cc
index 53c28032b8..6880b7c272 100644
--- a/cpp/src/arrow/compute/kernels/vector_cumulative_ops_test.cc
+++ b/cpp/src/arrow/compute/kernels/vector_cumulative_ops_test.cc
@@ -949,5 +949,34 @@ TEST(TestCumulative, NaN) {
   CheckVectorUnary("cumulative_mean", ArrayFromJSON(float64(), "[5, 4, NaN, 2, 
1]"),
                    ArrayFromJSON(float64(), "[5, 4.5, NaN, NaN, NaN]"));
 }
+
+TEST(TestCumulative, NegativeValues) {
+  // GH-51194: the default start for cumulative_max was initialized with
+  // std::numeric_limits<T>::min(), which is the smallest positive value for
+  // floating-point types, so non-positive values never replaced the start
+  CumulativeOptions options;
+  for (auto ty : SignedIntTypes()) {
+    CheckVectorUnary("cumulative_max", ArrayFromJSON(ty, "[-2, -1, -3]"),
+                     ArrayFromJSON(ty, "[-2, -1, -1]"), &options);
+    CheckVectorUnary("cumulative_min", ArrayFromJSON(ty, "[-2, -1, -3]"),
+                     ArrayFromJSON(ty, "[-2, -2, -3]"), &options);
+  }
+
+  for (auto ty : FloatingPointTypes()) {
+    CheckVectorUnary("cumulative_max", ArrayFromJSON(ty, "[-2.5, 2.5]"),
+                     ArrayFromJSON(ty, "[-2.5, 2.5]"), &options);
+    CheckVectorUnary("cumulative_min", ArrayFromJSON(ty, "[-2.5, 2.5]"),
+                     ArrayFromJSON(ty, "[-2.5, -2.5]"), &options);
+    CheckVectorUnary("cumulative_max", ArrayFromJSON(ty, "[-2.5, -1.5, -3.5, 
-0.5]"),
+                     ArrayFromJSON(ty, "[-2.5, -1.5, -1.5, -0.5]"), &options);
+
+    // The default start must compare lower (higher for min) than every value
+    // of the type, including infinities
+    CheckVectorUnary("cumulative_max", ArrayFromJSON(ty, "[-Inf, -2.5]"),
+                     ArrayFromJSON(ty, "[-Inf, -2.5]"), &options);
+    CheckVectorUnary("cumulative_min", ArrayFromJSON(ty, "[Inf, 2.5]"),
+                     ArrayFromJSON(ty, "[Inf, 2.5]"), &options);
+  }
+}
 }  // namespace compute
 }  // namespace arrow
diff --git a/python/pyarrow/tests/test_compute.py 
b/python/pyarrow/tests/test_compute.py
index d350c81157..8b2ad2b333 100644
--- a/python/pyarrow/tests/test_compute.py
+++ b/python/pyarrow/tests/test_compute.py
@@ -3625,6 +3625,28 @@ def test_cumulative_max(start, skip_nulls):
             pc.cumulative_max([1, 2, 3], start=strt)
 
 
[email protected]
+def test_cumulative_max_min_negative_default_start():
+    # GH-51194: the implicit start for cumulative_max was initialized with
+    # std::numeric_limits<T>::min(), which is the smallest positive value for
+    # floating-point types, so non-positive values never replaced the start
+    values = [-2.5, 2.5]
+    arr = pa.array(values, type=pa.float64())
+    assert pc.cumulative_max(arr).to_pylist() == [-2.5, 2.5]
+    assert pc.cumulative_min(arr).to_pylist() == [-2.5, -2.5]
+
+    arr = pa.chunked_array([[-2.5, -1.5], [-3.5, -0.5]])
+    assert pc.cumulative_max(arr).to_pylist() == [-2.5, -1.5, -1.5, -0.5]
+    assert pc.cumulative_min(arr).to_pylist() == [-2.5, -2.5, -3.5, -3.5]
+
+    # The default start must compare lower (higher for min) than every value
+    # of the type, including infinities
+    arr = pa.array([-np.inf, -2.5], type=pa.float64())
+    assert pc.cumulative_max(arr).to_pylist() == [-np.inf, -2.5]
+    arr = pa.array([np.inf, 2.5], type=pa.float64())
+    assert pc.cumulative_min(arr).to_pylist() == [np.inf, 2.5]
+
+
 @pytest.mark.numpy
 @pytest.mark.parametrize('start', (0.5, 3.5, 6.5))
 @pytest.mark.parametrize('skip_nulls', (True, False))

Reply via email to