Skip to content

Commit c1d1ff4

Browse files
authored
Merge pull request numpy#26892 from seberg/issue-26759
ENH: Allow use of clip with Python integers to always succeed
2 parents ca096a3 + 40836b6 commit c1d1ff4

File tree

3 files changed

+45
-10
lines changed

3 files changed

+45
-10
lines changed

numpy/_core/_methods.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import warnings
99
from contextlib import nullcontext
1010

11+
import numpy as np
1112
from numpy._core import multiarray as mu
1213
from numpy._core import umath as um
1314
from numpy._core.multiarray import asanyarray
@@ -97,10 +98,18 @@ def _count_reduce_items(arr, axis, keepdims=False, where=True):
9798
return items
9899

99100
def _clip(a, min=None, max=None, out=None, **kwargs):
101+
if a.dtype.kind in "iu":
102+
# If min/max is a Python integer, deal with out-of-bound values here.
103+
# (This enforces NEP 50 rules as no value based promotion is done.)
104+
if type(min) is int and min <= np.iinfo(a.dtype).min:
105+
min = None
106+
if type(max) is int and max >= np.iinfo(a.dtype).max:
107+
max = None
108+
100109
if min is None and max is None:
101110
# return identity
102111
return um.positive(a, out=out, **kwargs)
103-
if min is None:
112+
elif min is None:
104113
return um.minimum(a, max, out=out, **kwargs)
105114
elif max is None:
106115
return um.maximum(a, min, out=out, **kwargs)

numpy/_core/tests/test_multiarray.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5102,16 +5102,24 @@ def test_basic(self):
51025102
'uint', 1024, 10, 100, inplace=inplace)
51035103

51045104
@pytest.mark.parametrize("inplace", [False, True])
5105-
def test_int_range_error(self, inplace):
5106-
# E.g. clipping uint with negative integers fails to promote
5107-
# (changed with NEP 50 and may be adaptable)
5108-
# Similar to last check in `test_basic`
5105+
def test_int_out_of_range(self, inplace):
5106+
# Simple check for out-of-bound integers, also testing the in-place
5107+
# path.
51095108
x = (np.random.random(1000) * 255).astype("uint8")
5110-
with pytest.raises(OverflowError):
5111-
x.clip(-1, 10, out=x if inplace else None)
5112-
5113-
with pytest.raises(OverflowError):
5114-
x.clip(0, 256, out=x if inplace else None)
5109+
out = np.empty_like(x)
5110+
res = x.clip(-1, 300, out=out if inplace else None)
5111+
assert res is out or not inplace
5112+
assert (res == x).all()
5113+
5114+
res = x.clip(-1, 50, out=out if inplace else None)
5115+
assert res is out or not inplace
5116+
assert (res <= 50).all()
5117+
assert (res[x <= 50] == x[x <= 50]).all()
5118+
5119+
res = x.clip(100, 1000, out=out if inplace else None)
5120+
assert res is out or not inplace
5121+
assert (res >= 100).all()
5122+
assert (res[x >= 100] == x[x >= 100]).all()
51155123

51165124
def test_record_array(self):
51175125
rec = np.array([(-5, 2.0, 3.0), (5.0, 4.0, 3.0)],

numpy/_core/tests/test_numeric.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,6 +2880,24 @@ def test_clip_min_max_args(self):
28802880
with assert_raises_regex(ValueError, msg):
28812881
np.clip(arr, 2, 3, min=2)
28822882

2883+
@pytest.mark.parametrize("dtype,min,max", [
2884+
("int32", -2**32-1, 2**32),
2885+
("int32", -2**320, None),
2886+
("int32", None, 2**300),
2887+
("int32", -1000, 2**32),
2888+
("int32", -2**32-1, 1000),
2889+
("uint8", -1, 129),
2890+
])
2891+
def test_out_of_bound_pyints(self, dtype, min, max):
2892+
a = np.arange(10000).astype(dtype)
2893+
# Check min only
2894+
c = np.clip(a, min=min, max=max)
2895+
assert not np.may_share_memory(a, c)
2896+
assert c.dtype == a.dtype
2897+
if min is not None:
2898+
assert (c >= min).all()
2899+
if max is not None:
2900+
assert (c <= max).all()
28832901

28842902
class TestAllclose:
28852903
rtol = 1e-5

0 commit comments

Comments
 (0)