feat(numpy): add NPY_HALF_ to npy_api::constants (#6151)
* feat(numpy): add NPY_HALF_ to npy_api::constants
* tests: round-trip a user-defined half dtype as numpy.float16
diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h
index 22e11bc..5b1161d 100644
--- a/include/pybind11/numpy.h
+++ b/include/pybind11/numpy.h
@@ -235,6 +235,7 @@
NPY_STRING_,
NPY_UNICODE_,
NPY_VOID_,
+ 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 d6d79e2..6844cc0 100644
--- a/tests/test_numpy_dtypes.cpp
+++ b/tests/test_numpy_dtypes.cpp
@@ -318,6 +318,21 @@
struct A {};
struct B {};
+struct UserHalf {
+ uint16_t bits;
+};
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+PYBIND11_NAMESPACE_BEGIN(detail)
+template <>
+struct npy_format_descriptor<UserHalf> {
+ 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 @@
PYBIND11_NUMPY_DTYPE(TrailingPaddingStruct, a, b);
m.def("trailing_padding_dtype", []() { return py::dtype::of<TrailingPaddingStruct>(); });
+ // test_half_dtype (issue #4061)
+ m.def("half_dtype_num", []() { return py::dtype::num_of<UserHalf>(); });
+ m.def("half_roundtrip", [](const py::array_t<UserHalf> &arr) { return arr; });
+
// test_string_array
m.def("create_string_array", [](bool non_empty) {
py::array_t<StringStruct, 0> arr = mkarray_via_buffer<StringStruct>(non_empty ? 4 : 0);
diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py
index 13a696c..2e5a67b 100644
--- a/tests/test_numpy_dtypes.py
+++ b/tests/test_numpy_dtypes.py
@@ -205,6 +205,13 @@
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()