Skip to content

Sort NaNs to the end for descending order - #3066

Open
antonwolfy wants to merge 7 commits into
masterfrom
fix-descending-nan
Open

Sort NaNs to the end for descending order#3066
antonwolfy wants to merge 7 commits into
masterfrom
fix-descending-nan

Conversation

@antonwolfy

@antonwolfy antonwolfy commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

dpnp.sort, dpnp.argsort, and their dpnp.ndarray/dpnp.tensor counterparts placed NaN values at the beginning when sorting in descending order, while NumPy 2.5 keeps NaN values at the end for both ascending and descending order.

For descending order the sort kernels simply reversed the ascending comparison, which also moved NaN (treated as the largest value) to the front.

The PR proposes to change:

  • Merge-sort comparators: only the comparison between non-NaN values is reversed for descending order, so NaNs stay at the end. For complex values the NaN-ness groups (no nan) -> (imag nan) -> (real nan) -> (all nan) keep the same trailing order as ascending, and only the finite-component lexicographic comparison is reversed.
  • Radix-sort float casts (half / float32 / float64): map NaN to the maximum key, so it lands in the last bucket regardless of direction. This keeps the descending float radix fast path intact rather than falling back to a slower comparator.
  • dpnp.tensor.top_k inherits the change through the shared descending comparator: NaN values (and complex values with a NaN component) are now treated as the smallest, so mode="largest" no longer returns them ahead of finite values.

NaN ordering under sort/argsort is left implementation-defined by the Python Array API standard, and top_k explicitly permits sorting NaN values to either end. The new "NaN last" behavior is therefore spec-conforming and is chosen to match NumPy 2.5; it is now consistent across the dpnp and dpnp.tensor namespaces.

  • 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?

`dpnp.sort`/`dpnp.argsort` (and their `dpnp.ndarray`/`dpnp.tensor`
counterparts) placed `NaN` values first when sorting in descending
order, while NumPy 2.5 keeps `NaN` at the end for both ascending and
descending order.

Fix the merge-sort comparators so that only the comparison between
non-NaN values is reversed for descending order, and make the radix-sort
float casts map `NaN` to the maximum key so it lands in the last bucket
regardless of direction.
Bring in the descending-order sort/argsort test coverage from cupy#10088,
adapted to dpnp's signature (`order` remains a positional parameter, so the
keyword-only checks pass an explicit `order=None`).
Backport NumPy 2.5 descending-order test coverage (numpy gh-31345,
gh-31476, gh-31557) into dpnp's own sort test suite, adapted to the
dtypes dpnp supports:

* NaNs sort to the end for both ascending and descending float sorts.
* NaN-containing complex values sort to the end in the same groups for
  both orders, with finite values kept in lexicographic order.
* a stable (arg)sort keeps the original relative order of equal
  elements in both directions.

Expected results are delegated to `numpy.sort`/`numpy.argsort` with
`stable=True, descending=...`, so the tests require NumPy >= 2.5.
Note in the `dpnp` and `dpnp.tensor` sort/argsort docstrings that NaN
values (and complex values with a NaN component) are ordered to the end
regardless of the `descending` flag.
The descending sort fix routes float and complex `top_k(mode="largest")`
through the same comparator, so NaN values (and complex values with a NaN
component) are now treated as the smallest and no longer surface ahead of
finite values. Add a `dpnp.tensor.top_k` NaN test, document the behavior
in the `top_k` docstring, and add a changelog note.
Extend `test_sort_complex_fp_nan` to also sort with `descending=True` and
compare against NumPy, so the dpnp.tensor layer exercises the complex
NaN-at-end ordering for both directions. Guarded on numpy>=2.5.
@github-actions

Copy link
Copy Markdown
Contributor

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

@github-actions

Copy link
Copy Markdown
Contributor

Array API standard conformance tests for dpnp=0.21.0dev8=py314ha0e2e8e_15 ran successfully.
Passed: 1376
Failed: 0
Skipped: 6

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Status

coverage: 78.477%. remained the same — fix-descending-nan into master

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