古明地觉的编程教室

浮点数支持的操作是怎么实现的?

Image

楔子

本篇文章来聊一聊浮点数支持的操作,之前说过实例对象的相关操作都定义在类型对象里面,所以我们需要查看 PyFloat_Type。

// Objects/floatobject.c
PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),
    // ......
    // 浮点数的 __repr__ 方法
    (reprfunc)float_repr,              /* tp_repr */
    // 浮点数作为数值对象拥有的算术操作
    &float_as_number,                  /* tp_as_number */
    // 浮点数的哈希操作
    (hashfunc)float_hash,              /* tp_hash */
    // 浮点数支持的比较操作
    float_richcompare,                 /* tp_richcompare */
    // ......
};

还是之前说的,Python 底层的函数命名以及 API 都是很有规律的,举个例子:

  • tp_repr 字段表示实例对象的字符串格式化,在 PyFloat_Type 里面它被赋值为 float_repr。

  • tp_hash 字段表示实例对象的哈希操作,在 PyFloat_Type 里面它被赋值为 float_hash。

  • tp_richcompare 字段表示实例对象的比较操作,所有的比较运算均由该字段负责实现,在 PyFloat_Type 里面它被赋值为 float_richcompare。

下面我们来通过源码看一下底层实现。

Image

浮点数的字符串打印

由于 PyFloat_Type 没有实现 tp_str(字段的值为 0),所以打印一个浮点数会执行 tp_repr,它对应的具体实现为 float_repr 函数。

// Objects/floatobject.c
static PyObject *
float_repr(PyFloatObject *v)
{   
    PyObject *result;  // 返回值
    char *buf;
    // 将 Python 浮点数转成 C 的浮点数,然后再转成字符串
    buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
                                'r', 0,
                                Py_DTSF_ADD_DOT_0,
                                NULL);
    if (!buf)
        return PyErr_NoMemory();
    // 基于 C 字符串创建 Python 字符串
    result = _PyUnicode_FromASCII(buf, strlen(buf));
    // 释放 buf,然后返回
    PyMem_Free(buf);
    return result;
}

比较简单,当然具体的转换逻辑由 PyOS_double_to_string 函数负责,内部最终会调用 C 的库函数,感兴趣可以看一下。

Image

浮点数的哈希值

获取浮点数的哈希值会执行 tp_hash,它对应的具体实现为 float_hash。

// Objects/floatobject.c
static Py_hash_t
float_hash(PyFloatObject *v)
{
    return _Py_HashDouble((PyObject *)v, v->ob_fval);
}

具体的哈希计算逻辑由 _Py_HashDouble 负责,通过 v->ob_fval 拿到 C 浮点数,然后传进去计算哈希值。

Image

感兴趣可以看一下具体的哈希值计算逻辑,该函数位于 Python/pyhash.c 中。

Image

浮点数的比较操作

浮点数之间的比较操作由 tp_richcompare 字段负责实现,该字段的值为 float_richcompare。