Skip to content

Commit 9b00e3e

Browse files
committed
pythongh-111545: Add Py_HashDouble() function
Add tests: Modules/_testcapi/hash.c and Lib/test/test_capi/test_hash.py.
1 parent 6873555 commit 9b00e3e

File tree

7 files changed

+133
-7
lines changed

7 files changed

+133
-7
lines changed

Doc/c-api/hash.rst

+27
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ PyHash API
55

66
See also the :c:member:`PyTypeObject.tp_hash` member.
77

8+
Types
9+
^^^^^
10+
811
.. c:type:: Py_hash_t
912
1013
Hash value type: signed integer.
1114

1215
.. versionadded:: 3.2
1316

17+
1418
.. c:type:: Py_uhash_t
1519
1620
Hash value type: unsigned integer.
@@ -41,6 +45,29 @@ See also the :c:member:`PyTypeObject.tp_hash` member.
4145
.. versionadded:: 3.4
4246

4347

48+
Functions
49+
^^^^^^^^^
50+
51+
.. c:function:: Py_hash_t Py_HashDouble(double value)
52+
53+
Hash a C double number.
54+
55+
* If *value* is positive infinity, return :data:`sys.hash_info.inf
56+
<sys.hash_info>`.
57+
* If *value* is negative infinity, return :data:`-sys.hash_info.inf
58+
<sys.hash_info>`.
59+
* If *value* is not-a-number (NaN), return :data:`sys.hash_info.nan
60+
<sys.hash_info>` (``0``).
61+
* Otherwise, return the hash value of the finite *value* number.
62+
63+
.. note::
64+
Return the hash value ``0`` for the floating point numbers ``-0.0`` and
65+
``+0.0``, and for not-a-number (NaN). ``Py_IS_NAN(value)`` can be used to
66+
check if *value* is not-a-number.
67+
68+
.. versionadded:: 3.13
69+
70+
4471
.. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void)
4572
4673
Get the hash function definition.

Doc/whatsnew/3.13.rst

+15
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,21 @@ New Features
12811281
* Add :c:func:`Py_HashPointer` function to hash a pointer.
12821282
(Contributed by Victor Stinner in :gh:`111545`.)
12831283

1284+
* Add :c:func:`Py_HashDouble` function to hash a C double number. Existing code
1285+
using the private ``_Py_HashDouble()`` function can be updated to::
1286+
1287+
Py_hash_t hash_double(PyObject *obj, double value)
1288+
{
1289+
if (!Py_IS_NAN(value)) {
1290+
return Py_HashDouble(value);
1291+
}
1292+
else {
1293+
return Py_HashPointer(obj);
1294+
}
1295+
}
1296+
1297+
(Contributed by Victor Stinner in :gh:`111545`.)
1298+
12841299

12851300
Porting to Python 3.13
12861301
----------------------

Include/cpython/pyhash.h

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ typedef struct {
3737
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
3838

3939
PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);
40+
PyAPI_FUNC(Py_hash_t) Py_HashDouble(double value);

Lib/test/test_capi/test_hash.py

+41
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import sys
23
import unittest
34
from test.support import import_helper
@@ -77,3 +78,43 @@ def python_hash_pointer(x):
7778
# Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
7879
VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
7980
self.assertEqual(hash_pointer(VOID_P_MAX), -2)
81+
82+
def test_hash_double(self):
83+
# Test Py_HashDouble()
84+
hash_double = _testcapi.hash_double
85+
86+
# test some integers
87+
integers = [
88+
*range(1, 30),
89+
2**30 - 1,
90+
2 ** 233,
91+
int(sys.float_info.max),
92+
]
93+
for x in integers:
94+
with self.subTest(x=x):
95+
self.assertEqual(hash_double(float(x)), hash(x))
96+
self.assertEqual(hash_double(float(-x)), hash(-x))
97+
98+
# test positive and negative zeros
99+
self.assertEqual(hash_double(float(0.0)), 0)
100+
self.assertEqual(hash_double(float(-0.0)), 0)
101+
102+
# test +inf and -inf
103+
inf = float("inf")
104+
self.assertEqual(hash_double(inf), sys.hash_info.inf)
105+
self.assertEqual(hash_double(-inf), -sys.hash_info.inf)
106+
107+
# special float values: compare with Python hash() function
108+
special_values = (
109+
math.nextafter(0.0, 1.0), # smallest positive subnormal number
110+
sys.float_info.min, # smallest positive normal number
111+
sys.float_info.epsilon,
112+
sys.float_info.max, # largest positive finite number
113+
)
114+
for x in special_values:
115+
with self.subTest(x=x):
116+
self.assertEqual(hash_double(x), hash(x))
117+
self.assertEqual(hash_double(-x), hash(-x))
118+
119+
# test not-a-number (NaN)
120+
self.assertEqual(hash_double(float('nan')), 0)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`Py_HashDouble` function to hash a C double number. Patch by
2+
Victor Stinner.

Modules/_testcapi/hash.c

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
#include "parts.h"
22
#include "util.h"
33

4+
5+
static PyObject *
6+
long_from_hash(Py_hash_t hash)
7+
{
8+
assert(hash != -1);
9+
10+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
11+
return PyLong_FromLongLong(hash);
12+
}
13+
14+
415
static PyObject *
516
hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
617
{
@@ -54,14 +65,27 @@ hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
5465
}
5566

5667
Py_hash_t hash = Py_HashPointer(ptr);
57-
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
58-
return PyLong_FromLongLong(hash);
68+
return long_from_hash(hash);
69+
}
70+
71+
72+
static PyObject *
73+
hash_double(PyObject *Py_UNUSED(module), PyObject *args)
74+
{
75+
double value;
76+
if (!PyArg_ParseTuple(args, "d", &value)) {
77+
return NULL;
78+
}
79+
80+
Py_hash_t hash = Py_HashDouble(value);
81+
return long_from_hash(hash);
5982
}
6083

6184

6285
static PyMethodDef test_methods[] = {
6386
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
6487
{"hash_pointer", hash_pointer, METH_O},
88+
{"hash_double", hash_double, METH_VARARGS},
6589
{NULL},
6690
};
6791

Python/pyhash.c

+21-5
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,20 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};
8484
*/
8585

8686
Py_hash_t
87-
_Py_HashDouble(PyObject *inst, double v)
87+
Py_HashDouble(double v)
8888
{
8989
int e, sign;
9090
double m;
9191
Py_uhash_t x, y;
9292

9393
if (!Py_IS_FINITE(v)) {
94-
if (Py_IS_INFINITY(v))
95-
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
96-
else
97-
return _Py_HashPointer(inst);
94+
if (Py_IS_INFINITY(v)) {
95+
return (v > 0 ? _PyHASH_INF : -_PyHASH_INF);
96+
}
97+
else {
98+
assert(Py_IS_NAN(v));
99+
return 0;
100+
}
98101
}
99102

100103
m = frexp(v, &e);
@@ -129,6 +132,19 @@ _Py_HashDouble(PyObject *inst, double v)
129132
return (Py_hash_t)x;
130133
}
131134

135+
Py_hash_t
136+
_Py_HashDouble(PyObject *obj, double value)
137+
{
138+
assert(obj != NULL);
139+
140+
if (!Py_IS_NAN(value)) {
141+
return Py_HashDouble(value);
142+
}
143+
else {
144+
return Py_HashPointer(obj);
145+
}
146+
}
147+
132148
Py_hash_t
133149
Py_HashPointer(const void *ptr)
134150
{

0 commit comments

Comments
 (0)