From 7509e46bb0dbae63837f05395a560c21d22564c8 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 01:15:44 -0700 Subject: [PATCH 1/6] fix undefined behavior in casting float to unsigned integer casts could encounter undefined behavior when the float passes through a signed integral type, where the intermediate signed integral type could overflow --- dpnp/tensor/libtensor/include/utils/type_utils.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dpnp/tensor/libtensor/include/utils/type_utils.hpp b/dpnp/tensor/libtensor/include/utils/type_utils.hpp index 49353310686..6c787e5bf66 100644 --- a/dpnp/tensor/libtensor/include/utils/type_utils.hpp +++ b/dpnp/tensor/libtensor/include/utils/type_utils.hpp @@ -99,9 +99,12 @@ 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)); + // cast through sufficiently large signed integer type to preserve + // two's complement + using intermediateT = + std::conditional_t; + return static_cast(static_cast(v)); } else { return static_cast(v); From aca88974daec25f56eeb0058f56e255d86f30a35 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 01:18:08 -0700 Subject: [PATCH 2/6] add test for resolution to gh-2882 --- dpnp/tests/tensor/test_usm_ndarray_ctor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dpnp/tests/tensor/test_usm_ndarray_ctor.py b/dpnp/tests/tensor/test_usm_ndarray_ctor.py index b03a01ad370..5512dd2fcf8 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., 120.], 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] From 72c44166520bc20ef85114bfd74f1975c298211f Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 01:26:21 -0700 Subject: [PATCH 3/6] use int64 when casting floats to uint32 --- dpnp/tensor/libtensor/include/utils/type_utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/tensor/libtensor/include/utils/type_utils.hpp b/dpnp/tensor/libtensor/include/utils/type_utils.hpp index 6c787e5bf66..a2497116ccc 100644 --- a/dpnp/tensor/libtensor/include/utils/type_utils.hpp +++ b/dpnp/tensor/libtensor/include/utils/type_utils.hpp @@ -102,7 +102,7 @@ dstTy convert_impl(const srcTy &v) // cast through sufficiently large signed integer type to preserve // two's complement using intermediateT = - std::conditional_t; return static_cast(static_cast(v)); } From 20b4b18469452831677a38ebb6d28ee6525c6c25 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 02:14:41 -0700 Subject: [PATCH 4/6] only pass negative floats through signed integral type --- dpnp/tensor/libtensor/include/utils/type_utils.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dpnp/tensor/libtensor/include/utils/type_utils.hpp b/dpnp/tensor/libtensor/include/utils/type_utils.hpp index a2497116ccc..b27434be434 100644 --- a/dpnp/tensor/libtensor/include/utils/type_utils.hpp +++ b/dpnp/tensor/libtensor/include/utils/type_utils.hpp @@ -99,12 +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) { - // cast through sufficiently large signed integer type to preserve - // two's complement + // for negative values, cast through signed integer to get two's + // complement wrapping using intermediateT = - std::conditional_t; - return static_cast(static_cast(v)); + std::conditional_t; + return (v < srcTy{0}) + ? static_cast(static_cast(v)) + : static_cast(v); } else { return static_cast(v); From afb214f2ad0e882f6220794e1c5ecffeb8630bc4 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 02:14:56 -0700 Subject: [PATCH 5/6] add gh-2930 to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From f025367b4572916a0ddfc7f2fdaf9041098c97f5 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Fri, 22 May 2026 02:15:23 -0700 Subject: [PATCH 6/6] fix pre-commit --- dpnp/tensor/libtensor/include/utils/type_utils.hpp | 4 ++-- dpnp/tests/tensor/test_usm_ndarray_ctor.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dpnp/tensor/libtensor/include/utils/type_utils.hpp b/dpnp/tensor/libtensor/include/utils/type_utils.hpp index b27434be434..bb83c210b9f 100644 --- a/dpnp/tensor/libtensor/include/utils/type_utils.hpp +++ b/dpnp/tensor/libtensor/include/utils/type_utils.hpp @@ -102,8 +102,8 @@ dstTy convert_impl(const srcTy &v) // for negative values, cast through signed integer to get two's // complement wrapping using intermediateT = - std::conditional_t; + std::conditional_t; return (v < srcTy{0}) ? static_cast(static_cast(v)) : static_cast(v); diff --git a/dpnp/tests/tensor/test_usm_ndarray_ctor.py b/dpnp/tests/tensor/test_usm_ndarray_ctor.py index 5512dd2fcf8..59cb370118d 100644 --- a/dpnp/tests/tensor/test_usm_ndarray_ctor.py +++ b/dpnp/tests/tensor/test_usm_ndarray_ctor.py @@ -1103,7 +1103,7 @@ def test_astype_gh_2121(): def test_astype_gh_2882(): get_queue_or_skip() - x = dpt.asarray([160., 120.], dtype="f4") + 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)