diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a738267346..bad7c5a9c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This release is compatible with NumPy 2.4.5. * Fixed incorrect `dpnp.tensor.acosh` result for `complex(±0, NaN)` special case to match the Python Array API specification [#2914](https://github.com/IntelPython/dpnp/pull/2914) * Fixed fork PR documentation workflow failures by implementing conditional publishing strategy: upstream PRs publish to GitHub Pages with comment, fork PRs upload artifacts [#2910](https://github.com/IntelPython/dpnp/pull/2910) * Fixed missing `libtensor` headers in the installed `dpnp` package [#2915](https://github.com/IntelPython/dpnp/pull/2915) +* Fixed a bug in `astype` where casting floating point types to unsigned integral types could cause an intermediate signed integral type to overflow, leading to incorrect results [#2930](https://github.com/IntelPython/dpnp/pull/2930) ### Security diff --git a/dpnp/tensor/libtensor/include/utils/type_utils.hpp b/dpnp/tensor/libtensor/include/utils/type_utils.hpp index 49353310686..bb83c210b9f 100644 --- a/dpnp/tensor/libtensor/include/utils/type_utils.hpp +++ b/dpnp/tensor/libtensor/include/utils/type_utils.hpp @@ -99,9 +99,14 @@ dstTy convert_impl(const srcTy &v) else if constexpr (!std::is_integral_v && !std::is_same_v && std::is_integral_v && std::is_unsigned_v) { - // first cast to signed variant, the cast to unsigned one - using signedT = typename std::make_signed_t; - return static_cast(convert_impl(v)); + // for negative values, cast through signed integer to get two's + // complement wrapping + using intermediateT = + std::conditional_t; + return (v < srcTy{0}) + ? static_cast(static_cast(v)) + : static_cast(v); } else { return static_cast(v); diff --git a/dpnp/tests/tensor/test_usm_ndarray_ctor.py b/dpnp/tests/tensor/test_usm_ndarray_ctor.py index b03a01ad370..59cb370118d 100644 --- a/dpnp/tests/tensor/test_usm_ndarray_ctor.py +++ b/dpnp/tests/tensor/test_usm_ndarray_ctor.py @@ -1100,6 +1100,15 @@ def test_astype_gh_2121(): assert dpt.all(res == expected) +def test_astype_gh_2882(): + get_queue_or_skip() + + x = dpt.asarray([160.0, 120.0], dtype="f4") + r = dpt.astype(x, dpt.uint8) + expected = dpt.asarray([160, 120], dtype="u1") + assert dpt.all(r == expected) + + def test_copy(): try: X = dpt.usm_ndarray((5, 5), "i4")[2:4, 1:4]