Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ This release is compatible with NumPy 2.5.
* Fixed a crash in boolean-mask advanced indexing (`dpnp.ndarray` get/set item) when the selection is empty (e.g. a scalar `False` index that injects a length-0 axis) [#3019](https://github.com/IntelPython/dpnp/pull/3019)
* Released the GIL before the remaining blocking OneMKL BLAS and LAPACK calls to prevent host tasks contention, completing the work started in [#2850](https://github.com/IntelPython/dpnp/pull/2850) [#3027](https://github.com/IntelPython/dpnp/pull/3027)
* Fixed `dpnp.repeat` raising an unclear `TypeError` for a nested sequence of `repeats` [#3024](https://github.com/IntelPython/dpnp/pull/3024)
* Fixed `astype` casting an out-of-range floating point value to a signed narrow integer type saturating to the destination min/max instead of wrapping like NumPy, generalizing the earlier unsigned-only fix [#3033](https://github.com/IntelPython/dpnp/pull/3033)

### Security

Expand Down
27 changes: 18 additions & 9 deletions dpnp/tensor/libtensor/include/utils/type_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,24 @@ dstTy convert_impl(const srcTy &v)
}
else if constexpr (!std::is_integral_v<srcTy> &&
!std::is_same_v<dstTy, bool> &&
std::is_integral_v<dstTy> && std::is_unsigned_v<dstTy>) {
// for negative values, cast through signed integer to get two's
// complement wrapping
using intermediateT =
std::conditional_t<sizeof(dstTy) < sizeof(std::int32_t),
std::int32_t, std::int64_t>;
return (v < srcTy{0})
? static_cast<dstTy>(static_cast<intermediateT>(v))
: static_cast<dstTy>(v);
std::is_integral_v<dstTy>) {
// Out-of-range float-to-int casts are UB; SYCL saturates while NumPy
// wraps. Funnel through a wider signed integer so the well-defined
// integer narrowing reproduces NumPy's wrapping, e.g. f32(128) ->
// i8(-128).
if constexpr (sizeof(dstTy) < sizeof(std::int64_t)) {
return static_cast<dstTy>(static_cast<std::int64_t>(v));
}
else if constexpr (std::is_unsigned_v<dstTy>) {
// uint64: no wider signed type, so only negatives need int64
return (v < srcTy{0})
? static_cast<dstTy>(static_cast<std::int64_t>(v))
: static_cast<dstTy>(v);
}
else {
// int64: nothing wider to funnel through
return static_cast<dstTy>(v);
}
}
else {
return static_cast<dstTy>(v);
Expand Down
40 changes: 20 additions & 20 deletions dpnp/tests/tensor/test_usm_ndarray_ctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,12 @@
import dpnp.tensor as dpt
from dpnp.tensor import Device

from .elementwise.utils import _all_dtypes, _integral_dtypes, _real_fp_dtypes
from .helper import (
get_queue_or_skip,
skip_if_dtype_not_supported,
)

_all_dtypes = [
"b1",
"i1",
"u1",
"i2",
"u2",
"i4",
"u4",
"i8",
"u8",
"f2",
"f4",
"f8",
"c8",
"c16",
]


@pytest.mark.parametrize(
"shape",
Expand Down Expand Up @@ -1039,6 +1023,22 @@ def test_astype_gh_2882():
assert dpt.all(r == expected)


@pytest.mark.usefixtures("suppress_overflow_encountered_in_cast_numpy_warnings")
@pytest.mark.parametrize("dst_dtype", _integral_dtypes)
@pytest.mark.parametrize("src_dtype", _real_fp_dtypes)
def test_astype_out_of_range_float_to_int(src_dtype, dst_dtype):
q = get_queue_or_skip()
skip_if_dtype_not_supported(src_dtype, q)

values = [0, 1, -1, 127, 128, -129, 255, 256, -256, 300, 60000, -60000]
x_np = np.asarray(values, dtype=src_dtype)
x = dpt.asarray(x_np, sycl_queue=q)

expected = x_np.astype(dst_dtype)
res = dpt.astype(x, dst_dtype)
assert dpt.all(res == dpt.asarray(expected, sycl_queue=q))


def test_copy():
try:
X = dpt.usm_ndarray((5, 5), "i4")[2:4, 1:4]
Expand Down Expand Up @@ -1350,7 +1350,7 @@ def test_full_dtype_inference():
assert np.issubdtype(dpt.full(10, 0.3 - 2j, dtype=rdt).dtype, np.floating)


@pytest.mark.parametrize("dt", ["f2", "f4", "f8"])
@pytest.mark.parametrize("dt", _real_fp_dtypes)
def test_full_special_fp(dt):
"""See gh-1314"""
q = get_queue_or_skip()
Expand Down Expand Up @@ -1434,7 +1434,7 @@ def test_full_strides():
assert np.array_equal(dpt.asnumpy(X), Xnp)


@pytest.mark.parametrize("dt", ["i1", "u1", "i2", "u2", "i4", "u4", "i8", "u8"])
@pytest.mark.parametrize("dt", _integral_dtypes)
def test_full_gh_1230(dt):
get_queue_or_skip()
dtype = dpt.dtype(dt)
Expand Down Expand Up @@ -1551,7 +1551,7 @@ def test_linspace_fp():
assert X.strides == (1,)


@pytest.mark.parametrize("dtype", ["f2", "f4", "f8"])
@pytest.mark.parametrize("dtype", _real_fp_dtypes)
def test_linspace_fp_max(dtype):
q = get_queue_or_skip()
skip_if_dtype_not_supported(dtype, q)
Expand Down
Loading