Skip to content

Commit 5cff6c1

Browse files
author
peng.li24
committed
refactor(numpycpp): update elementwise_py.h and add buffer_dtype.h
1 parent 7d2fb30 commit 5cff6c1

2 files changed

Lines changed: 164 additions & 98 deletions

File tree

numpycpp/detail/buffer_dtype.h

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// ════════════════════════════════════════════════════════════════════════════
2+
// numpycpp — detail/buffer_dtype.h [INTERNAL HEADER]
3+
//
4+
// 零 Python 调用的 dtype 检测工具。从 py::buffer_info (C API PyObject_GetBuffer
5+
// 一次性获取的 C 结构体) 解析数组类型,不触发任何 Python 属性访问。
6+
//
7+
// 安全用于任何调用上下文——包括 Python→C++→C++ 的嵌套调用链。
8+
//
9+
// 用法:
10+
// auto buf = arr.request();
11+
// auto bd = numpy::detail::analyze_buffer(buf);
12+
// if (bd.is_float32()) { ... }
13+
// ════════════════════════════════════════════════════════════════════════════
14+
#pragma once
15+
16+
#include <pybind11/numpy.h>
17+
#include <stdexcept>
18+
#include <string>
19+
20+
namespace numpy {
21+
namespace detail {
22+
23+
enum class BufKind { FLOAT32, FLOAT64, INT32, INT64, BOOL, UNKNOWN };
24+
25+
struct BufDtype {
26+
BufKind kind;
27+
int itemsize;
28+
char fmt_char; // raw format char after stripping byte-order prefix
29+
30+
bool is_float32() const { return kind == BufKind::FLOAT32; }
31+
bool is_float64() const { return kind == BufKind::FLOAT64; }
32+
bool is_int32() const { return kind == BufKind::INT32; }
33+
bool is_int64() const { return kind == BufKind::INT64; }
34+
bool is_bool() const { return kind == BufKind::BOOL; }
35+
bool is_float() const { return is_float32() || is_float64(); }
36+
};
37+
38+
/// 纯 C++ 解析 buffer_info::format + itemsize,零 Python 调用。
39+
/// PEP 3118 format 字符: f=float, d=double, i/l=int, q=longlong, ?/b=bool.
40+
/// 字节序前缀 @/=/</>/! 被自动剥离。
41+
inline BufDtype analyze_buffer(const py::buffer_info& buf) {
42+
char c = buf.format.empty() ? '\0' : buf.format[0];
43+
if (c == '@' || c == '=' || c == '<' || c == '>' || c == '!')
44+
c = buf.format.size() > 1 ? buf.format[1] : '\0';
45+
46+
int sz = static_cast<int>(buf.itemsize);
47+
BufKind kind = BufKind::UNKNOWN;
48+
49+
switch (c) {
50+
case 'f':
51+
kind = (sz == 4) ? BufKind::FLOAT32
52+
: (sz == 8) ? BufKind::FLOAT64 : BufKind::UNKNOWN;
53+
break;
54+
case 'd':
55+
kind = (sz == 8) ? BufKind::FLOAT64 : BufKind::UNKNOWN;
56+
break;
57+
case 'i': case 'l':
58+
kind = (sz == 4) ? BufKind::INT32
59+
: (sz == 8) ? BufKind::INT64 : BufKind::UNKNOWN;
60+
break;
61+
case 'q':
62+
kind = (sz == 8) ? BufKind::INT64 : BufKind::UNKNOWN;
63+
break;
64+
case '?': case 'b':
65+
kind = BufKind::BOOL;
66+
break;
67+
default:
68+
break;
69+
}
70+
return {kind, sz, c};
71+
}
72+
73+
/// 目标 dtype 字符串 → BufKind + itemsize。
74+
/// 必须显式指定精度: "float64"/"double", "float32", "int"/"int32", "int64", "bool"。
75+
/// 不支持模糊的 "float" — 避免 numpy float=float64 的隐式约定歧义。
76+
inline BufDtype target_dtype(const std::string& dtype) {
77+
if (dtype == "float64" || dtype == "double")
78+
return {BufKind::FLOAT64, 8, 'd'};
79+
if (dtype == "float32")
80+
return {BufKind::FLOAT32, 4, 'f'};
81+
if (dtype == "int" || dtype == "int32")
82+
return {BufKind::INT32, 4, 'i'};
83+
if (dtype == "int64")
84+
return {BufKind::INT64, 8, 'q'};
85+
if (dtype == "bool")
86+
return {BufKind::BOOL, 1, '?'};
87+
return {BufKind::UNKNOWN, 0, '\0'};
88+
}
89+
90+
} // namespace detail
91+
} // namespace numpy

numpycpp/elementwise_py.h

Lines changed: 73 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <stdexcept>
2020
#include <cstdint>
2121
#include <type_traits>
22+
#include "detail/buffer_dtype.h"
2223

2324
namespace py = pybind11;
2425

@@ -263,110 +264,84 @@ DEF_SPECIAL(isfinite)
263264
// Type conversion
264265
// ============================================================================
265266

266-
/// ndarray.astype(dtype) — unified dtype dispatch
267+
/// ndarray.astype(dtype) — unified dtype dispatch.
268+
/// 零 Python 调用: 仅通过 buf.format + itemsize (C API) 检测源类型;
269+
/// 不使用 py::dtype::is() / kind(),避免 Python↔C++ 递归。
270+
/// 目标必须显式指定精度: "float64"/"double", "float32", 不接受模糊的 "float"。
267271
inline py::array astype(const py::array& arr, const std::string& dtype) {
268-
auto buf = arr.request();
269-
auto dt = arr.dtype();
270-
271-
// py::dtype::of<float>() / py::dtype::of<double>() 在 Python 传入的
272-
// numpy 数组上可能不匹配(已知 pybind11 问题)。用 buffer format + itemsize
273-
// 回退——buf.format 来自 C API 的 PyObject_GetBuffer,不触发 Python 属性调用,
274-
// 避免 astype 内递归。
275-
char _fmt_char = buf.format.empty() ? '\0' :
276-
(buf.format[0] == '<' || buf.format[0] == '>' || buf.format[0] == '=')
277-
? buf.format[1] : buf.format[0];
278-
bool _is_f32 = (_fmt_char == 'f' && buf.itemsize == 4);
279-
bool _is_f64 = (_fmt_char == 'd' && buf.itemsize == 8) ||
280-
(_fmt_char == 'f' && buf.itemsize == 8);
281-
bool _is_i32 = (_fmt_char == 'i' && buf.itemsize == 4) ||
282-
(_fmt_char == 'l' && buf.itemsize == 4);
283-
bool _is_i64 = (_fmt_char == 'i' && buf.itemsize == 8) ||
284-
(_fmt_char == 'l' && buf.itemsize == 8) ||
285-
(_fmt_char == 'q' && buf.itemsize == 8);
286-
bool _is_bool = (_fmt_char == '?' || _fmt_char == 'b');
287-
288-
#define _ASTYPE_MATCH(SrcT) \
289-
(dt.is(py::dtype::of<SrcT>()) || \
290-
(std::is_same<SrcT, float>::value && _is_f32) || \
291-
(std::is_same<SrcT, double>::value && _is_f64) || \
292-
(std::is_same<SrcT, int>::value && _is_i32) || \
293-
(std::is_same<SrcT, int64_t>::value&& _is_i64) || \
294-
(std::is_same<SrcT, bool>::value && _is_bool))
295-
296-
#define _ASTYPE_CASE(SrcT, dst_str, DstT) \
297-
if (_ASTYPE_MATCH(SrcT) && (dtype == dst_str)) { \
298-
py::array_t<DstT> r(buf.shape); \
299-
numpy::astype<DstT, SrcT>(static_cast<const SrcT*>(buf.ptr), \
300-
static_cast<DstT*>(r.request().ptr), buf.size); \
301-
return r; \
272+
auto buf = arr.request(); // C API — 零 Python
273+
auto src = detail::analyze_buffer(buf); // 纯 C++ — 零 Python
274+
auto dst = detail::target_dtype(dtype);
275+
276+
if (dst.kind == detail::BufKind::UNKNOWN) {
277+
auto dt = arr.dtype(); // 仅错误路径: 获取 dtype 名称用于报错
278+
throw std::runtime_error(
279+
"astype: unsupported target dtype '" + dtype + "'. "
280+
"Available: float64/double, float32, int/int32, int64, bool. "
281+
"Source: " + std::string(py::str(dt)));
302282
}
303283

304-
// ═══════════════════════════════════════════════════════════════════════
305-
// 目标字符串语义(严格对齐 numpy ndarray.astype() 行为):
306-
//
307-
// "float64" / "double" → C++ double (64-bit)
308-
// "float32" → C++ float (32-bit)
309-
// "float" → C++ double (64-bit) ← 注意!numpy 中
310-
// np.float64(1).astype(float) → float64
311-
// np.float32(1).astype(float) → float64
312-
// np.int32(1).astype(float) → float64
313-
// 即 numpy 默认 float = float64,不是 float32
314-
// "int" / "int32" → C++ int (32-bit)
315-
// "int64" → C++ int64_t
316-
// "bool" → C++ bool
317-
// ═══════════════════════════════════════════════════════════════════════
318-
// float64
319-
_ASTYPE_CASE(double, "float64", double)
320-
_ASTYPE_CASE(double, "double", double)
321-
_ASTYPE_CASE(double, "float32", float)
322-
_ASTYPE_CASE(double, "float", double) // numpy: float64.astype(float) → float64
323-
_ASTYPE_CASE(double, "int", int)
324-
_ASTYPE_CASE(double, "int32", int)
325-
_ASTYPE_CASE(double, "int64", int64_t)
326-
_ASTYPE_CASE(double, "bool", bool)
327-
// float32
328-
_ASTYPE_CASE(float, "float64", double)
329-
_ASTYPE_CASE(float, "double", double)
330-
_ASTYPE_CASE(float, "float", double) // "float" → float64 (numpy 默认)
331-
_ASTYPE_CASE(float, "float32", float)
332-
_ASTYPE_CASE(float, "int", int)
333-
_ASTYPE_CASE(float, "int32", int)
334-
_ASTYPE_CASE(float, "int64", int64_t)
335-
_ASTYPE_CASE(float, "bool", bool)
336-
// int32
337-
_ASTYPE_CASE(int, "int32", int)
338-
_ASTYPE_CASE(int, "int", int)
339-
_ASTYPE_CASE(int, "float64", double)
340-
_ASTYPE_CASE(int, "double", double)
341-
_ASTYPE_CASE(int, "float32", float)
342-
_ASTYPE_CASE(int, "float", double) // "float" → float64 (numpy 默认)
343-
_ASTYPE_CASE(int, "int64", int64_t)
344-
_ASTYPE_CASE(int, "bool", bool)
345-
// int64
346-
_ASTYPE_CASE(int64_t, "int64", int64_t)
347-
_ASTYPE_CASE(int64_t, "float64", double)
348-
_ASTYPE_CASE(int64_t, "double", double)
349-
_ASTYPE_CASE(int64_t, "float32", float)
350-
_ASTYPE_CASE(int64_t, "float", double) // "float" → float64 (numpy 默认)
351-
_ASTYPE_CASE(int64_t, "int", int)
352-
_ASTYPE_CASE(int64_t, "int32", int)
353-
_ASTYPE_CASE(int64_t, "bool", bool)
354-
// bool
355-
_ASTYPE_CASE(bool, "bool", bool)
356-
_ASTYPE_CASE(bool, "float64", double)
357-
_ASTYPE_CASE(bool, "double", double)
358-
_ASTYPE_CASE(bool, "float32", float)
359-
_ASTYPE_CASE(bool, "float", double) // "float" → float64 (numpy 默认)
360-
_ASTYPE_CASE(bool, "int", int)
361-
_ASTYPE_CASE(bool, "int32", int)
362-
_ASTYPE_CASE(bool, "int64", int64_t)
363-
#undef _ASTYPE_CASE
364-
#undef _ASTYPE_MATCH
284+
// dispatch: 源类型 × 目标类型 → 调用对应模板实例
285+
switch (src.kind) {
286+
case detail::BufKind::FLOAT64:
287+
switch (dst.kind) {
288+
case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,double>(static_cast<const double*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; }
289+
case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, double>(static_cast<const double*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; }
290+
case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, double>(static_cast<const double*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; }
291+
case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,double>(static_cast<const double*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; }
292+
case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, double>(static_cast<const double*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; }
293+
default: break;
294+
}
295+
break;
296+
case detail::BufKind::FLOAT32:
297+
switch (dst.kind) {
298+
case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,float>(static_cast<const float*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; }
299+
case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, float>(static_cast<const float*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; }
300+
case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, float>(static_cast<const float*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; }
301+
case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,float>(static_cast<const float*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; }
302+
case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, float>(static_cast<const float*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; }
303+
default: break;
304+
}
305+
break;
306+
case detail::BufKind::INT32:
307+
switch (dst.kind) {
308+
case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,int>(static_cast<const int*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; }
309+
case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, int>(static_cast<const int*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; }
310+
case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, int>(static_cast<const int*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; }
311+
case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,int>(static_cast<const int*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; }
312+
case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, int>(static_cast<const int*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; }
313+
default: break;
314+
}
315+
break;
316+
case detail::BufKind::INT64:
317+
switch (dst.kind) {
318+
case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; }
319+
case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; }
320+
case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; }
321+
case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; }
322+
case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, int64_t>(static_cast<const int64_t*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; }
323+
default: break;
324+
}
325+
break;
326+
case detail::BufKind::BOOL:
327+
switch (dst.kind) {
328+
case detail::BufKind::FLOAT64: { py::array_t<double> r(buf.shape); numpy::astype<double,bool>(static_cast<const bool*>(buf.ptr),static_cast<double*>(r.request().ptr),buf.size); return r; }
329+
case detail::BufKind::FLOAT32: { py::array_t<float> r(buf.shape); numpy::astype<float, bool>(static_cast<const bool*>(buf.ptr),static_cast<float*>(r.request().ptr),buf.size); return r; }
330+
case detail::BufKind::INT32: { py::array_t<int> r(buf.shape); numpy::astype<int, bool>(static_cast<const bool*>(buf.ptr),static_cast<int*>(r.request().ptr),buf.size); return r; }
331+
case detail::BufKind::INT64: { py::array_t<int64_t>r(buf.shape); numpy::astype<int64_t,bool>(static_cast<const bool*>(buf.ptr),static_cast<int64_t*>(r.request().ptr),buf.size); return r; }
332+
case detail::BufKind::BOOL: { py::array_t<bool> r(buf.shape); numpy::astype<bool, bool>(static_cast<const bool*>(buf.ptr),static_cast<bool*>(r.request().ptr),buf.size); return r; }
333+
default: break;
334+
}
335+
break;
336+
default:
337+
break;
338+
}
365339

340+
auto dt = arr.dtype(); // 仅错误路径
366341
throw std::runtime_error(
367342
"astype: unsupported conversion " + std::string(py::str(dt)) +
368-
" -> " + dtype + ". Available targets: float64/double, float32/float, "
369-
"int/int32, int64, bool.");
343+
" -> " + dtype + ". Available targets: float64/double, float, "
344+
"float32, int/int32, int64, bool.");
370345
}
371346

372347
/// float64 → float32 → float64 roundtrip

0 commit comments

Comments
 (0)