diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst index 91d88ae27bc9f4..5e6cbcc3826140 100644 --- a/Doc/c-api/hash.rst +++ b/Doc/c-api/hash.rst @@ -5,12 +5,16 @@ PyHash API See also the :c:member:`PyTypeObject.tp_hash` member. +Types +^^^^^ + .. c:type:: Py_hash_t Hash value type: signed integer. .. versionadded:: 3.2 + .. c:type:: Py_uhash_t Hash value type: unsigned integer. @@ -41,6 +45,27 @@ See also the :c:member:`PyTypeObject.tp_hash` member. .. versionadded:: 3.4 +Functions +^^^^^^^^^ + +.. c:function:: int Py_HashDouble(double value, Py_hash_t *result) + + Hash a C double number. + + * Set *\*result* to the hash value and return ``1`` on success. + * Set *\*result* to ``0`` and return ``0`` if the hash value cannot be + calculated. For example, if *value* is not-a-number (NaN). + + *result* must not be ``NULL``. + + .. note:: + Only rely on the function return value to distinguish the "not-a-number" + case. *\*result* can be ``0`` if *value* is finite. For example, + ``Py_HashDouble(0.0, &result)`` sets *\*result* to 0. + + .. versionadded:: 3.13 + + .. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void) Get the hash function definition. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index d599ba9ae6fac8..832536eb9e89c1 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1274,6 +1274,21 @@ New Features * Add :c:func:`Py_HashPointer` function to hash a pointer. (Contributed by Victor Stinner in :gh:`111545`.) +* Add :c:func:`Py_HashDouble` function to hash a C double number. Existing code + using the private ``_Py_HashDouble()`` function can be updated to:: + + Py_hash_t + hash_double(PyObject *obj, double value) + { + Py_hash_t hash; + if (Py_HashDouble(value, &hash) == 0) { + hash = Py_HashPointer(obj); + } + return hash; + } + + (Contributed by Victor Stinner in :gh:`111545`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/cpython/pyhash.h b/Include/cpython/pyhash.h index 396c208e1b106a..f495723cc99d9a 100644 --- a/Include/cpython/pyhash.h +++ b/Include/cpython/pyhash.h @@ -37,3 +37,4 @@ typedef struct { PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void); PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr); +PyAPI_FUNC(int) Py_HashDouble(double value, Py_hash_t *result); diff --git a/Lib/test/test_capi/test_hash.py b/Lib/test/test_capi/test_hash.py index 8436da7c32df10..5d20f61f202718 100644 --- a/Lib/test/test_capi/test_hash.py +++ b/Lib/test/test_capi/test_hash.py @@ -1,3 +1,4 @@ +import math import sys import unittest from test.support import import_helper @@ -77,3 +78,46 @@ def python_hash_pointer(x): # Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2 VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1) self.assertEqual(hash_pointer(VOID_P_MAX), -2) + + def test_hash_double(self): + # Test Py_HashDouble() + hash_double = _testcapi.hash_double + + def check_number(value, expected): + self.assertEqual(hash_double(value), (1, expected)) + + # test some integers + integers = [ + *range(1, 30), + 2**30 - 1, + 2 ** 233, + int(sys.float_info.max), + ] + for x in integers: + with self.subTest(x=x): + check_number(float(x), hash(x)) + check_number(float(-x), hash(-x)) + + # test positive and negative zeros + check_number(float(0.0), 0) + check_number(float(-0.0), 0) + + # test +inf and -inf + inf = float("inf") + check_number(inf, sys.hash_info.inf) + check_number(-inf, -sys.hash_info.inf) + + # special float values: compare with Python hash() function + special_values = ( + math.nextafter(0.0, 1.0), # smallest positive subnormal number + sys.float_info.min, # smallest positive normal number + sys.float_info.epsilon, + sys.float_info.max, # largest positive finite number + ) + for x in special_values: + with self.subTest(x=x): + check_number(x, hash(x)) + check_number(-x, hash(-x)) + + # test not-a-number (NaN) + self.assertEqual(hash_double(float('nan')), (0, 0)) diff --git a/Misc/NEWS.d/next/C API/2023-12-06-15-32-30.gh-issue-111545.kSOygi.rst b/Misc/NEWS.d/next/C API/2023-12-06-15-32-30.gh-issue-111545.kSOygi.rst new file mode 100644 index 00000000000000..b6f1db895a3523 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-12-06-15-32-30.gh-issue-111545.kSOygi.rst @@ -0,0 +1,2 @@ +Add :c:func:`Py_HashDouble` function to hash a C double number. Patch by +Victor Stinner. diff --git a/Modules/_testcapi/hash.c b/Modules/_testcapi/hash.c index aee76787dcddb3..cadb45a61b9b4d 100644 --- a/Modules/_testcapi/hash.c +++ b/Modules/_testcapi/hash.c @@ -1,6 +1,17 @@ #include "parts.h" #include "util.h" + +static PyObject * +long_from_hash(Py_hash_t hash) +{ + assert(hash != -1); + + Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash)); + return PyLong_FromLongLong(hash); +} + + static PyObject * hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { @@ -54,14 +65,28 @@ hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg) } Py_hash_t hash = Py_HashPointer(ptr); - Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash)); - return PyLong_FromLongLong(hash); + return long_from_hash(hash); +} + + +static PyObject * +hash_double(PyObject *Py_UNUSED(module), PyObject *args) +{ + double value; + if (!PyArg_ParseTuple(args, "d", &value)) { + return NULL; + } + + Py_hash_t hash; + int res = Py_HashDouble(value, &hash); + return Py_BuildValue("iN", res, long_from_hash(hash)); } static PyMethodDef test_methods[] = { {"hash_getfuncdef", hash_getfuncdef, METH_NOARGS}, {"hash_pointer", hash_pointer, METH_O}, + {"hash_double", hash_double, METH_VARARGS}, {NULL}, }; diff --git a/Python/pyhash.c b/Python/pyhash.c index 141407c265677a..a59899084f4a78 100644 --- a/Python/pyhash.c +++ b/Python/pyhash.c @@ -83,18 +83,23 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0}; */ -Py_hash_t -_Py_HashDouble(PyObject *inst, double v) +int +Py_HashDouble(double v, Py_hash_t *result) { int e, sign; double m; Py_uhash_t x, y; if (!Py_IS_FINITE(v)) { - if (Py_IS_INFINITY(v)) - return v > 0 ? _PyHASH_INF : -_PyHASH_INF; - else - return _Py_HashPointer(inst); + if (Py_IS_INFINITY(v)) { + *result = (v > 0 ? _PyHASH_INF : -_PyHASH_INF); + return 1; + } + else { + assert(Py_IS_NAN(v)); + *result = 0; + return 0; + } } m = frexp(v, &e); @@ -126,7 +131,20 @@ _Py_HashDouble(PyObject *inst, double v) x = x * sign; if (x == (Py_uhash_t)-1) x = (Py_uhash_t)-2; - return (Py_hash_t)x; + *result = (Py_hash_t)x; + return 1; +} + +Py_hash_t +_Py_HashDouble(PyObject *obj, double value) +{ + assert(obj != NULL); + + Py_hash_t hash; + if (Py_HashDouble(value, &hash) == 0) { + hash = Py_HashPointer(obj); + } + return hash; } Py_hash_t pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy