From 80071371601233408b4d919dfe7cc05cfee93d02 Mon Sep 17 00:00:00 2001 From: AnkitAhlawat Date: Thu, 30 Apr 2026 19:18:10 +0530 Subject: [PATCH] BUG: Fix signed overflow issue in npy_gcd for INT_MIN on s390x (#31360) This PR fixes signed overflow issue in npy_gcd when one of the inputs is the minimum value of a signed integer type (INT_MIN) on s390x architecture. issue was the expression a < 0 ? -a : a in npy_math_internal.h.src causes undefined behavior when a = INT_MIN because negating INT_MIN overflows a signed integer on s390x. i see it is working fine with other arch so fix only provided to s390x --- numpy/_core/src/npymath/npy_math_internal.h.src | 11 ++++++++++- numpy/_core/tests/test_umath.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/numpy/_core/src/npymath/npy_math_internal.h.src b/numpy/_core/src/npymath/npy_math_internal.h.src index 2f38497..787f7e6 100644 --- a/numpy/_core/src/npymath/npy_math_internal.h.src +++ b/numpy/_core/src/npymath/npy_math_internal.h.src @@ -650,12 +650,21 @@ npy_lcm@c@(@type@ a, @type@ b) * * #type = (npy_int, npy_long, npy_longlong)*2# * #c = (,l,ll)*2# + * #utype = (npy_uint, npy_ulong, npy_ulonglong)*2# * #func=gcd*3,lcm*3# */ NPY_INPLACE @type@ npy_@func@@c@(@type@ a, @type@ b) { - return npy_@func@u@c@(a < 0 ? -a : a, b < 0 ? -b : b); + /* + * Cast to unsigned to avoid overflow when negating minimum signed value. + * For negative values, cast to unsigned first, then apply negation in + * unsigned arithmetic to avoid undefined behavior. + * This ensures correct behavior across all architectures. #31359 + */ + @utype@ a_abs = a < 0 ? (@utype@)(0) - (@utype@)a : (@utype@)a; + @utype@ b_abs = b < 0 ? (@utype@)(0) - (@utype@)b : (@utype@)b; + return (@type@)npy_@func@u@c@(a_abs, b_abs); } /**end repeat**/ diff --git a/numpy/_core/tests/test_umath.py b/numpy/_core/tests/test_umath.py index 8eac236..1575dd6 100644 --- a/numpy/_core/tests/test_umath.py +++ b/numpy/_core/tests/test_umath.py @@ -4131,13 +4131,17 @@ def test_lcm_overflow(self): assert_equal(np.lcm(a, b), 10 * big) def test_gcd_overflow(self): - for dtype in (np.int32, np.int64): - # verify that we don't overflow when taking abs(x) - # not relevant for lcm, where the result is unrepresentable anyway - a = dtype(np.iinfo(dtype).min) # negative power of two + # verify that we don't overflow when taking abs(x) for INT_MIN + # this was undefined behavior that manifested on s390x with GCC 11.5 + for dtype in (np.int8, np.int16, np.int32, np.int64): + a = dtype(np.iinfo(dtype).min) # INT_MIN q = -(a // 4) + # Test with INT_MIN as first argument assert_equal(np.gcd(a, q * 3), q) assert_equal(np.gcd(a, -q * 3), q) + # Test with INT_MIN as second argument + assert_equal(np.gcd(q * 3, a), q) + assert_equal(np.gcd(-q * 3, a), q) def test_decimal(self): from decimal import Decimal -- 2.54.0