summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release/upcoming_changes/14981.compatibility.rst10
-rw-r--r--numpy/core/tests/test_regression.py8
-rw-r--r--numpy/lib/arraysetops.py26
-rw-r--r--numpy/lib/tests/test_arraysetops.py19
4 files changed, 41 insertions, 22 deletions
diff --git a/doc/release/upcoming_changes/14981.compatibility.rst b/doc/release/upcoming_changes/14981.compatibility.rst
new file mode 100644
index 000000000..90cf866f2
--- /dev/null
+++ b/doc/release/upcoming_changes/14981.compatibility.rst
@@ -0,0 +1,10 @@
+`np.ediff1d` casting behaviour with ``to_end`` and ``to_begin``
+---------------------------------------------------------------
+
+`np.ediff1d` now uses the ``"same_kind"`` casting rule for
+its additional ``to_end`` and ``to_begin`` arguments. This
+ensures type safety except when the input array has a smaller
+integer type than ``to_begin`` or ``to_end``.
+In rare cases, the behaviour will be more strict than it was
+previously in 1.16 and 1.17. This is necessary to solve issues
+with floating point NaN.
diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py
index d5de0f2b2..b215163ae 100644
--- a/numpy/core/tests/test_regression.py
+++ b/numpy/core/tests/test_regression.py
@@ -2467,8 +2467,11 @@ class TestRegression(object):
x = np.array([1, 2, 4, 7, 0], dtype=np.int16)
res = np.ediff1d(x, to_begin=-99, to_end=np.array([88, 99]))
assert_equal(res, [-99, 1, 2, 3, -7, 88, 99])
- assert_raises(ValueError, np.ediff1d, x, to_begin=(1<<20))
- assert_raises(ValueError, np.ediff1d, x, to_end=(1<<20))
+
+ # The use of safe casting means, that 1<<20 is cast unsafely, an
+ # error may be better, but currently there is no mechanism for it.
+ res = np.ediff1d(x, to_begin=(1<<20), to_end=(1<<20))
+ assert_equal(res, [0, 1, 2, 3, -7, 0])
def test_pickle_datetime64_array(self):
# gh-12745 (would fail with pickle5 installed)
@@ -2513,3 +2516,4 @@ class TestRegression(object):
assert arr.size * arr.itemsize > 2 ** 31
c_arr = np.ctypeslib.as_ctypes(arr)
assert_equal(c_arr._length_, arr.size)
+
diff --git a/numpy/lib/arraysetops.py b/numpy/lib/arraysetops.py
index 2309f7e42..d65316598 100644
--- a/numpy/lib/arraysetops.py
+++ b/numpy/lib/arraysetops.py
@@ -94,8 +94,7 @@ def ediff1d(ary, to_end=None, to_begin=None):
# force a 1d array
ary = np.asanyarray(ary).ravel()
- # enforce propagation of the dtype of input
- # ary to returned result
+ # enforce that the dtype of `ary` is used for the output
dtype_req = ary.dtype
# fast track default case
@@ -105,22 +104,23 @@ def ediff1d(ary, to_end=None, to_begin=None):
if to_begin is None:
l_begin = 0
else:
- _to_begin = np.asanyarray(to_begin, dtype=dtype_req)
- if not np.all(_to_begin == to_begin):
- raise ValueError("cannot convert 'to_begin' to array with dtype "
- "'%r' as required for input ary" % dtype_req)
- to_begin = _to_begin.ravel()
+ to_begin = np.asanyarray(to_begin)
+ if not np.can_cast(to_begin, dtype_req, casting="same_kind"):
+ raise TypeError("dtype of `to_end` must be compatible "
+ "with input `ary` under the `same_kind` rule.")
+
+ to_begin = to_begin.ravel()
l_begin = len(to_begin)
if to_end is None:
l_end = 0
else:
- _to_end = np.asanyarray(to_end, dtype=dtype_req)
- # check that casting has not overflowed
- if not np.all(_to_end == to_end):
- raise ValueError("cannot convert 'to_end' to array with dtype "
- "'%r' as required for input ary" % dtype_req)
- to_end = _to_end.ravel()
+ to_end = np.asanyarray(to_end)
+ if not np.can_cast(to_end, dtype_req, casting="same_kind"):
+ raise TypeError("dtype of `to_end` must be compatible "
+ "with input `ary` under the `same_kind` rule.")
+
+ to_end = to_end.ravel()
l_end = len(to_end)
# do the calculation in place and copy to_begin and to_end
diff --git a/numpy/lib/tests/test_arraysetops.py b/numpy/lib/tests/test_arraysetops.py
index fd21a7f76..1d38d8d27 100644
--- a/numpy/lib/tests/test_arraysetops.py
+++ b/numpy/lib/tests/test_arraysetops.py
@@ -135,9 +135,9 @@ class TestSetOps(object):
None,
np.nan),
# should fail because attempting
- # to downcast to smaller int type:
- (np.array([1, 2, 3], dtype=np.int16),
- np.array([5, 1<<20, 2], dtype=np.int32),
+ # to downcast to int type:
+ (np.array([1, 2, 3], dtype=np.int64),
+ np.array([5, 7, 2], dtype=np.float32),
None),
# should fail because attempting to cast
# two special floating point values
@@ -152,8 +152,8 @@ class TestSetOps(object):
# specifically, raise an appropriate
# Exception when attempting to append or
# prepend with an incompatible type
- msg = 'cannot convert'
- with assert_raises_regex(ValueError, msg):
+ msg = 'must be compatible'
+ with assert_raises_regex(TypeError, msg):
ediff1d(ary=ary,
to_end=append,
to_begin=prepend)
@@ -163,9 +163,13 @@ class TestSetOps(object):
"append,"
"expected", [
(np.array([1, 2, 3], dtype=np.int16),
- 0,
+ 2**16, # will be cast to int16 under same kind rule.
+ 2**16 + 4,
+ np.array([0, 1, 1, 4], dtype=np.int16)),
+ (np.array([1, 2, 3], dtype=np.float32),
+ np.array([5], dtype=np.float64),
None,
- np.array([0, 1, 1], dtype=np.int16)),
+ np.array([5, 1, 1], dtype=np.float32)),
(np.array([1, 2, 3], dtype=np.int32),
0,
0,
@@ -187,6 +191,7 @@ class TestSetOps(object):
to_end=append,
to_begin=prepend)
assert_equal(actual, expected)
+ assert actual.dtype == expected.dtype
def test_isin(self):