Skip to content

Wrap out-of-range float-to-integer astype casts to match NumPy - #3033

Draft
antonwolfy wants to merge 2 commits into
masterfrom
fix/astype-float-to-signed-int-wrap
Draft

Wrap out-of-range float-to-integer astype casts to match NumPy#3033
antonwolfy wants to merge 2 commits into
masterfrom
fix/astype-float-to-signed-int-wrap

Conversation

@antonwolfy

@antonwolfy antonwolfy commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Casting an out-of-range floating-point value to an integer type is undefined behavior in C++. SYCL devices resolve it by saturating to the destination's min/max, whereas NumPy emits a plain C cast that, for narrow integer targets, truncates toward zero and wraps modulo the destination width (e.g. float32(128) becomes int8(-128)).

convert_impl (used by astype and every copy-and-cast kernel) only normalized this to NumPy's wrapping behavior for unsigned destinations. Signed narrow integer targets fell through to a raw static_cast and therefore saturated on device, diverging from NumPy. The same inconsistency showed up within a single operation: dpnp.tensor.linalg.trace with an int8 output dtype produced different results depending on whether the value went through the element-wise astype path (saturated) or the reduction path (wrapped).

This PR generalizes the float-to-integer branch of convert_impl to funnel every conversion through a wider signed integer, relying on the well-defined integer narrowing to perform the modular wrap for both signed and unsigned narrow integer targets. 64-bit destinations keep their existing handling (unsigned routes negatives through int64; signed casts directly).

  • Have you provided a meaningful PR description?
  • Have you added a test, reproducer or referred to an issue with a reproducer?
  • Have you tested your changes locally for CPU and GPU devices?
  • Have you made sure that new changes do not introduce compiler warnings?
  • Have you checked performance impact of proposed changes?
  • Have you added documentation for your changes, if necessary?
  • Have you added your changes to the changelog?

Casting an out-of-range floating-point value to an integer type is
undefined behavior in C++. SYCL devices resolve it by saturating to the
destination's min/max, while NumPy emits a plain C cast that, for narrow
integer targets, truncates toward zero and wraps modulo the destination
width (e.g. float32(128) -> int8(-128)).

dpnp targets NumPy compatibility, but convert_impl only normalized this
for unsigned destinations, so signed narrow targets saturated instead of
wrapping. This inconsistency surfaced as an Array API conformance failure
in linalg.trace with an int8 output dtype, where the element-wise astype
path saturated while the reduction path wrapped.

Generalize convert_impl to funnel every float-to-integer conversion
through a wider signed integer, relying on the well-defined integer
narrowing to perform the modular wrap for both signed and unsigned narrow
targets. Add a regression test covering signed and unsigned targets and
reuse the shared dtype lists in the ctor tests.
@antonwolfy antonwolfy added this to the 0.21.0 release milestone Aug 19, 2026
@antonwolfy antonwolfy self-assigned this Aug 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Array API standard conformance tests for dpnp=0.21.0dev5=py314ha0e2e8e_5 ran successfully.
Passed: 1376
Failed: 1
Skipped: 5

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Status

coverage: 78.458%. remained the same — fix/astype-float-to-signed-int-wrap into master

@github-actions

Copy link
Copy Markdown
Contributor

View rendered docs @ https://intelpython.github.io/dpnp/pull/3033/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants