From b34e4c89084f3680b4fd22b9352f70be24794d02 Mon Sep 17 00:00:00 2001 From: Advit Arora Date: Mon, 24 Aug 2026 02:29:52 +0530 Subject: [PATCH 1/2] feat(numpy): add NPY_HALF_ to npy_api::constants --- include/pybind11/numpy.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 22e11bcbad..de661e43b5 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -235,6 +235,8 @@ struct npy_api { NPY_STRING_, NPY_UNICODE_, NPY_VOID_, + // NumPy's NPY_DATETIME (21) and NPY_TIMEDELTA (22) are not mirrored here. + NPY_HALF_ = 23, // Platform-dependent normalization NPY_INT8_ = NPY_BYTE_, NPY_UINT8_ = NPY_UBYTE_, From 568bd1dc3e499e41872f53e8100135ddf4200400 Mon Sep 17 00:00:00 2001 From: Advit Arora Date: Mon, 24 Aug 2026 02:29:52 +0530 Subject: [PATCH 2/2] tests: round-trip a user-defined half dtype as numpy.float16 --- include/pybind11/numpy.h | 3 +-- tests/test_numpy_dtypes.cpp | 19 +++++++++++++++++++ tests/test_numpy_dtypes.py | 7 +++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index de661e43b5..5b1161d8c8 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -235,8 +235,7 @@ struct npy_api { NPY_STRING_, NPY_UNICODE_, NPY_VOID_, - // NumPy's NPY_DATETIME (21) and NPY_TIMEDELTA (22) are not mirrored here. - NPY_HALF_ = 23, + NPY_HALF_ = 23, // NPY_DATETIME (21) and NPY_TIMEDELTA (22) are not mirrored // Platform-dependent normalization NPY_INT8_ = NPY_BYTE_, NPY_UINT8_ = NPY_UBYTE_, diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index d6d79e2fb8..6844cc095a 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -318,6 +318,21 @@ py::array_t dispatch_array_increment(const py::array_t &arr) { struct A {}; struct B {}; +struct UserHalf { + uint16_t bits; +}; + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) +template <> +struct npy_format_descriptor { + static constexpr auto name = const_name("numpy.float16"); + static constexpr int value = npy_api::NPY_HALF_; + static pybind11::dtype dtype() { return pybind11::dtype(/*typenum*/ value); } +}; +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + TEST_SUBMODULE(numpy_dtypes, m) { try { py::module_::import("numpy"); @@ -646,6 +661,10 @@ TEST_SUBMODULE(numpy_dtypes, m) { PYBIND11_NUMPY_DTYPE(TrailingPaddingStruct, a, b); m.def("trailing_padding_dtype", []() { return py::dtype::of(); }); + // test_half_dtype (issue #4061) + m.def("half_dtype_num", []() { return py::dtype::num_of(); }); + m.def("half_roundtrip", [](const py::array_t &arr) { return arr; }); + // test_string_array m.def("create_string_array", [](bool non_empty) { py::array_t arr = mkarray_via_buffer(non_empty ? 4 : 0); diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index 13a696c1b5..2e5a67ba8f 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -205,6 +205,13 @@ def test_dtype(simple_dtype): assert (m.test_dtype_switch(arr.astype("longdouble")) == arr + 1).all() +def test_half_dtype(): + assert m.half_dtype_num() == np.dtype("float16").num + + result = m.half_roundtrip(np.array([1.5, 2.25, -3.0], dtype=np.float16)) + assert result.dtype == np.float16 + + def test_templated_dtype(): """A type spelled with a comma needs PYBIND11_TYPE here.""" plain, renamed = m.templated_dtypes()