ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CPython 浮点数 C API 全解析:PyFloatObject、类型检查、特殊值宏与 IEEE 754 打包/解包

CPython 浮点数 C API 全解析:PyFloatObject、类型检查、特殊值宏与 IEEE 754 打包/解包 CPython 浮点数 C API 全解析PyFloatObject、类型检查、特殊值宏与 IEEE 754 打包/解包【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本文围绕 CPython 官方 C API 文档 Doc/c-api/float.rst 中定义的“Floating-Point Objects”章节展开系统讲解PyFloatObject数据结构、PyFloat_Type类型对象、创建/转换函数PyFloat_FromDouble、PyFloat_FromString、PyFloat_AsDouble、边界值与元信息函数PyFloat_GetMax/GetMin/GetInfo、特殊值与数学常量宏Py_INFINITY、Py_NAN、Py_MATH_PI等以及 3.11 新增的PyFloat_Pack2/4/8与PyFloat_Unpack2/4/8序列化接口。结合 Objects/floatobject.c 的源码实现读者可以掌握在 C 扩展中安全创建、检查、转换和序列化浮点对象的完整方法。核心数据结构与类型对象PyFloatObject一个 PyObject_HEAD 加一个 doubleCPython 的浮点对象结构极其精简定义在 Include/cpython/floatobject.htypedef struct { PyObject_HEAD double ob_fval; } PyFloatObject;即标准对象头引用计数、类型指针之后紧跟一个 Cdouble ob_fval字段。头文件注释明确说明它代表“double precision双精度浮点数”。这意味着 Python 层唯一的浮点类型就是 C 的doublesys.float_info中报告的精度、指数范围等全部来自平台float.h的DBL_*宏。访问内部值的标准方式是PyFloat_AS_DOUBLE宏它是一个 static inline 实现见 Include/cpython/floatobject.h// Static inline version of PyFloat_AsDouble() trading safety for speed. // It doesnt check if op is a double object. static inline double PyFloat_AS_DOUBLE(PyObject *op) { return _PyFloat_CAST(op)-ob_fval; }与官方文档一致PyFloat_AS_DOUBLE直接取ob_fval不做任何类型检查调用者必须自己保证参数是PyFloatObject。PyFloat_Type 与类型检查宏PyFloat_Type是float类型的PyTypeObject实例与 Python 层内置的float是同一个对象。其完整类型定义含tp_repr、tp_richcompare、数值方法表等在 Objects/floatobject.c 中。两个类型检查宏定义在 Include/floatobject.h#define PyFloat_Check(op) PyObject_TypeCheck(op, PyFloat_Type) #define PyFloat_CheckExact(op) Py_IS_TYPE((op), PyFloat_Type)PyFloat_Check(op)参数是PyFloatObject或其子类时返回真总是成功不抛异常PyFloat_CheckExact(op)仅当参数精确为PyFloatObject不接受子类时返回真总是成功。从源码结构看PyObject_TypeCheck走的是“精确匹配或沿tp_base链向上匹配”的逻辑因此PyFloat_Check对float的子类实例也会为真——这与 Python 层isinstance(x, float)的语义一致在扩展中区分“精确 float”和“float 及其子类”时应选用对应的宏。创建浮点对象PyFloat_FromDouble 与 PyFloat_FromStringPyFloat_FromDoublePyObject* PyFloat_FromDouble(double v);从 Cdouble创建PyFloatObject失败返回NULL。实现在 Objects/floatobject.cPyObject * PyFloat_FromDouble(double fval) { PyFloatObject *op _Py_FREELIST_POP(PyFloatObject, floats); if (op NULL) { op PyObject_Malloc(sizeof(PyFloatObject)); if (!op) { return PyErr_NoMemory(); } _PyObject_Init((PyObject*)op, PyFloat_Type); } op-ob_fval fval; return (PyObject *) op; }值得注意的实现细节小对象缓存freelistCPython 为 float 维护了一个 freelist回收的PyFloatObject见 float_dealloc会被放回缓存供下次复用这是float类型对象分配/释放开销极低的原因唯一失败路径是内存分配失败此时设置MemoryError并返回NULL调用方必须检查返回值由于double是值类型NaN、±inf、有符号零都可以无损传入——PyFloat_FromDouble(Py_NAN)就是文档中Py_RETURN_NAN宏的展开形式。PyFloat_FromStringPyObject* PyFloat_FromString(PyObject *str);从字符串值创建浮点对象失败返回NULL。实现见 Objects/floatobject.c它对输入做了比文档更丰富的处理接受strUnicode、bytes、bytearray甚至任意实现了 buffer 协议的对象对 Unicode 输入会先做 ASCII 规范化_PyUnicode_TransformDecimalAndSpaceToASCII兼容非 ASCII 的十进制字符和 Unicode 空格经由_Py_string_to_number_with_underscores支持数字下划线如1_000.5内部函数float_from_string_inner同文件会剥离首尾空白然后调用PyOS_string_to_double完成解析整个字符串必须被消费否则抛ValueError: could not convert string to float: ...注释中明确“不关心溢出/下溢”——溢出会得到inf下溢会得到带符号零这是平台行为而非错误。Python 层float()的字符串入口正是它float_new_implObjects/floatobject.c中当参数是精确的str时直接调用PyFloat_FromString否则退回PyNumber_Float。提取 C doublePyFloat_AsDouble 与 PyFloat_AS_DOUBLEdouble PyFloat_AsDouble(PyObject *pyfloat);官方文档强调的语义是参数不是 float 但定义了__float__方法时先调用之再没有则回退到__index__3.8 起的行为。实现在 Objects/floatobject.c逻辑链为NULL参数 → 设置TypeErrorPyErr_BadArgument返回-1PyFloat_Check通过 → 直接走PyFloat_AS_DOUBLE快路径否则查tp_as_number-nb_float有则调用__float__并对返回非精确 float的结果发出DeprecationWarning__float__返回严格子类已弃用nb_float缺失但存在nb_index→ 通过_PyNumber_Index调用__index__再经PyLong_AsDouble转 double这就是“回退到__index__”的来源都没有 →TypeError: must be real number, not ...。错误约定失败时返回-1.0必须用PyErr_Occurred()判别-1.0本身是合法浮点值double x PyFloat_AsDouble(obj); if (x -1.0 PyErr_Occurred()) { return NULL; /* 传播异常 */ }对比之下PyFloat_AS_DOUBLE无错误检查用于已确认类型的热路径。边界值与 float 元信息GetMax / GetMin / GetInfo函数返回值底层来源PyFloat_GetMax()可表示的最大有限 floatDBL_MAXPyFloat_GetMin()最小正规化正 float注意不是最小可表示值DBL_MIN两者实现极薄见 Objects/floatobject.c分别直接return DBL_MAX;/return DBL_MIN;。PyFloat_GetInfo()返回一个 structseq 实例是 C 层获取 Python 层sys.float_info内容的途径。从 Objects/floatobject.c 的字段表可以看到它包含11 个字段max、max_exp、max_10_exp、min、min_exp、min_10_exp、dig、mant_dig、epsilon、radix、rounds全部直接取自float.hDBL_MAX、DBL_MAX_EXP、DBL_DIG、DBL_EPSILON、FLT_RADIX、FLT_ROUNDS等。这正印证了文档描述“Its a thin wrapper around the header filefloat.h.” 该类型在解释器初始化时由_PyFloat_InitTypes注册为内置 structseq 类型。特殊值与数学常量宏这些宏定义在 Include/pymath.h 和 Include/floatobject.h文档逐一说明了它们的含义与弃用状态宏作用源码事实Py_INFINITY正无穷常量表达式#define Py_INFINITY ((double)INFINITY)等价于 C11math.h的INFINITY3.15 起软弃用建议直接用INFINITYPy_NAN静默 NaNqNaN绝大多数平台为((double)NAN)Solaris 上NAN可能是函数地址故回退到__builtin_nanf()或strtod(NAN, NULL)见 Include/pymath.hPy_HUGE_VAL等价于INFINITY历史上因平台不完全符合 IEEE/C99 而与Py_INFINITY不一致3.14 起软弃用Py_MATH_Emath.e的 double 精度定义2.7182818284590452354Py_MATH_Elmath.e的long double高精度定义3.15 弃用、3.20 移除Py_MATH_PImath.pi的 double 精度定义3.14159265358979323846Py_MATH_PIlmath.pi的long double高精度定义3.15 弃用、3.20 移除Py_MATH_TAUmath.tau的定义6.2831853071795864769...L3.6 加入Py_IS_FINITE(X)X 为有限值非 inf/NaN返回 1展开为 C11isfinite(X)3.14 起软弃用建议直接用isfinitePy_IS_INFINITY(X)X 为 ±inf 返回 1展开为isinf(X)3.14 起软弃用Py_IS_NAN(X)X 为 NaN 返回 1展开为isnan(X)3.14 起软弃用从 Include/pymath.h 的源码注释可以确认这些“软弃用”的时间线Soft deprecated since Python 3.14/3.15新代码应优先使用 C 标准库的isnan/isinf/isfinite与INFINITY/NAN。Py_RETURN_NAN 与 Py_RETURN_INFInclude/floatobject.h 给出了与文档一致的展开#define Py_RETURN_NAN return PyFloat_FromDouble(Py_NAN) #define Py_RETURN_INF(sign) \ do { \ if (copysign(1., sign) 1.) { \ return PyFloat_FromDouble(INFINITY); \ } \ else { \ return PyFloat_FromDouble(-INFINITY); \ } \ } while(0)Py_RETURN_INF(sign)依据sign的符号位决定返回math.inf还是-math.inf语义上等价于return PyFloat_FromDouble(copysign(INFINITY, sign));是 C 扩展中返回无穷大的惯用写法。Pack / Unpack平台无关的 IEEE 754 序列化3.11 新增官方文档指出pack/unpack 提供“高效且平台无关”的字节串存储方式。格式对应关系2 字节IEEE 754 binary16 半精度half-precision4 字节IEEE 754 binary32 单精度single-precision8 字节IEEE 754 binary64 双精度double-precision。两组函数均在 Include/cpython/floatobject.h 声明实现在 Objects/floatobject.c。Pack 函数int PyFloat_Pack2(double x, char *p, int le); int PyFloat_Pack4(double x, char *p, int le); int PyFloat_Pack8(double x, char *p, int le);向自p起的 2/4/8 字节写入结果le非零要求小端指数字节在p1/p3/p6、p7零要求大端指数在p处使用 PY_LITTLE_ENDIAN 常量 选择本机字节序它在WORDS_BIGENDIAN由 configure 检测并写入 pyconfig.h定义时为0否则为1返回值0成功-1失败且已设置异常最常见的是OverflowError。从源码可以看到各格式的具体约束Pack8Objects/floatobject.c直接把double的 8 字节内存拷贝出来仅在字节序不匹配时逐字节倒序——因为double本身就是 binary64所以 Pack8 不存在数值损失Pack4同文件先float y (float)x;若x有限但转换后变 inf抛OverflowError: float too large to pack with f format对 sNaN→qNaN 的转换做了逐位修复含 RISC-V 平台的特殊处理Pack2同文件手工构造 half 的 16 位布局——1 位符号 5 位指数0x1f表示 inf/NaN 10 位尾数e 16时溢出OverflowError: float too large to pack with e format|x| 2**-25下溢为零2**-25 ≤ |x| 2**-14走渐进下溢gradual underflow路径舍入采用 round-to-even注释中说明与 NumPy 的NPY_HALF_ROUND_TIES_TO_EVEN行为保持一致。Unpack 函数double PyFloat_Unpack2(const char *p, int le); double PyFloat_Unpack4(const char *p, int le); double PyFloat_Unpack8(const char *p, int le);自p读取 2/4/8 字节le的语义与 Pack 相同返回解包出的 double出错时返回-1.0且PyErr_Occurred()为真异常同样是OverflowError一类Unpack2会把 5 位指数、10 位尾数还原为 doubleldexp组合指数全 1 时区分 Infinity 与 NaN并保留 NaN 的载荷位Unpack4对 sNaN 做了还原处理返回 sNaN 的 double 表示。文档特别提示两点平台限制值得在扩展中牢记NaN 类型可能不保留某些平台解包时 signaling NaN 会变成 quiet NaN例如 x86 32 位模式非 IEEE 平台的精度/动态范围差异函数假设double是 binary64若平台double精度更高或范围更大pack 可能丢值精度更低或范围更小时unpack 可能无法表示所有值解包含 IEEE INF/NaN 的字节串甚至可能抛异常。CPython 测试基建中提供了直接调用这 6 个函数的 C 测试入口 Modules/_testcapi/float.c可用于验证本机构造的字节序与特殊值行为Python 层的浮点打包行为则由 Lib/test/test_float.py 中的struct.pack对照测试覆盖如test_float.py中struct.pack(f, 3.40282356e38)与FLT_MAX的等价断言。实战示例在 C 扩展中安全使用 float API综合以上接口一个典型的扩展函数“读取 Python float、越界检查、返回带符号无穷或 NaN”可以写成#include Python.h /* 从 Python float 读值做范围判断返回处理结果 */ static PyObject * float_demo(PyObject *module, PyObject *args) { PyObject *obj; if (!PyArg_ParseTuple(args, O, obj)) { return NULL; } double x PyFloat_AsDouble(obj); /* 允许 __float__/__index__ 回退 */ if (x -1.0 PyErr_Occurred()) { return NULL; /* 必须用 PyErr_Occurred 判别 */ } if (!isfinite(x)) { return Py_RETURN_NAN; /* 非有限值 - math.nan */ } double limit PyFloat_GetMax(); /* DBL_MAX */ double v x * 1e300; if (isinf(v)) { Py_RETURN_INF(x); /* 按 x 符号返回 ±math.inf */ } return PyFloat_FromDouble(v); /* 常规路径检查 NULL 更严谨 */ }要点回顾需要“接受 int/Decimal/自定义数值”时用PyFloat_AsDouble需要“确认是 float 再提速”时用PyFloat_CheckExactPyFloat_AS_DOUBLE所有PyFloat_FromDouble的返回值在内存极度紧张时可能为NULL严格代码应检查序列化到字节流日志、网络、文件时用PyFloat_Pack8等函数而不是直接memcpy8 字节——后者无法跨字节序平台读取。相关源码与文档索引资源路径说明C API 文档Doc/c-api/float.rst本文对应的官方接口说明公开头文件Include/floatobject.hPyFloat_Type、检查宏、Py_RETURN_NAN/INF内部头文件Include/cpython/floatobject.hPyFloatObject结构、PyFloat_AS_DOUBLE、Pack/Unpack 声明核心实现Objects/floatobject.c类型定义、freelist、数值方法、Pack/Unpack数学宏Include/pymath.hPy_NAN、Py_MATH_*、Py_IS_*及弃用注释字节序常量Include/pyport.hPY_LITTLE_ENDIAN/PY_BIG_ENDIANC 测试入口Modules/_testcapi/float.cPack/Unpack 的测试 C APIPython 测试Lib/test/test_float.py浮点行为测试含 struct.pack 对照需要注意的前提本文基于当前仓库版本Python 3.x 主开发分支Py_INFINITY3.15 软弃用、Py_IS_*系列3.14 软弃用、Py_MATH_El/Py_MATH_PIl3.15 弃用、计划 3.20 移除等时间线以 Include/pymath.h 中的注释与 Doc/c-api/float.rst 的soft-deprecated/deprecated-removed标记为准跨版本移植扩展代码时应核对目标版本的这些标记。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表