Skip to content

Commit a8c4cb2

Browse files
committed
pythongh-111545: Add PyHash_Double() function
* Cleanup _Py_HashDouble() implementation. * Add Lib/test/test_capi/test_hash.py. * Add Modules/_testcapi/hash.c.
1 parent 62802b6 commit a8c4cb2

File tree

13 files changed

+144
-19
lines changed

13 files changed

+144
-19
lines changed

Doc/c-api/hash.rst

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. highlight:: c
2+
3+
PyHash API
4+
----------
5+
6+
See also the :c:member:`PyTypeObject.tp_hash` member.
7+
8+
.. c:function:: Py_hash_t PyHash_Double(double value)
9+
10+
Hash a C double number.
11+
12+
Return ``-1`` if *value* is not-a-number (NaN).

Doc/c-api/utilities.rst

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and parsing function arguments and constructing Python values from C values.
1717
marshal.rst
1818
arg.rst
1919
conversion.rst
20+
hash.rst
2021
reflection.rst
2122
codec.rst
2223
perfmaps.rst

Doc/whatsnew/3.13.rst

+3
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,9 @@ New Features
11811181
:exc:`KeyError` if the key missing.
11821182
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
11831183

1184+
* Add :c:func:`PyHash_Double` function to hash a C double number.
1185+
(Contributed by Victor Stinner in :gh:`111545`.)
1186+
11841187

11851188
Porting to Python 3.13
11861189
----------------------

Include/cpython/pyhash.h

+2
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ typedef struct {
1111
} PyHash_FuncDef;
1212

1313
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);
14+
15+
PyAPI_FUNC(Py_hash_t) PyHash_Double(double value);

Lib/test/test_capi/test_hash.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import math
2+
import sys
3+
import unittest
4+
from test.support import import_helper
5+
_testcapi = import_helper.import_module('_testcapi')
6+
7+
8+
PyHASH_INF = 314159
9+
if _testcapi.SIZEOF_VOID_P >= 8:
10+
PyHASH_BITS = 61
11+
else:
12+
PyHASH_BITS = 31
13+
PyHASH_MODULUS = ((1 << PyHASH_BITS) - 1)
14+
15+
16+
class CAPITest(unittest.TestCase):
17+
def test_hash_double(self):
18+
# Test PyHash_Double()
19+
hash_double = _testcapi.hash_double
20+
21+
# test integers
22+
integers = [*range(0, 10), 2**30 - 1, 2 ** 233]
23+
integers.extend([-x for x in integers])
24+
for x in integers:
25+
expected = abs(x) % PyHASH_MODULUS
26+
if x < 0:
27+
expected = -expected
28+
if expected == -1:
29+
expected = -2
30+
self.assertEqual(hash_double(float(x)), expected, x)
31+
32+
# test non-finite values
33+
self.assertEqual(hash_double(float('inf')), PyHASH_INF)
34+
self.assertEqual(hash_double(float('-inf')), -PyHASH_INF)
35+
self.assertEqual(hash_double(float('nan')), -1)
36+
37+
# special values: compare with Python hash() function
38+
def python_hash_double(x):
39+
return hash(x)
40+
41+
special_values = (
42+
sys.float_info.max,
43+
sys.float_info.min,
44+
sys.float_info.epsilon,
45+
math.nextafter(0.0, 1.0),
46+
)
47+
for x in special_values:
48+
with self.subTest(x=x):
49+
self.assertEqual(hash_double(x), python_hash_double(x))
50+
self.assertEqual(hash_double(-x), python_hash_double(-x))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyHash_Double` function to hash a C double number. Patch by
2+
Victor Stinner.

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
160160
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
161161
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
162-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c
162+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c
163163
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
164164
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
165165

Modules/_testcapi/hash.c

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
static PyObject *
5+
hash_double(PyObject *Py_UNUSED(module), PyObject *args)
6+
{
7+
double value;
8+
if (!PyArg_ParseTuple(args, "d", &value)) {
9+
return NULL;
10+
}
11+
Py_hash_t hash = PyHash_Double(value);
12+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
13+
return PyLong_FromLongLong(hash);
14+
}
15+
16+
static PyMethodDef test_methods[] = {
17+
{"hash_double", hash_double, METH_VARARGS},
18+
{NULL},
19+
};
20+
21+
int
22+
_PyTestCapi_Init_Hash(PyObject *m)
23+
{
24+
return PyModule_AddFunctions(m, test_methods);
25+
}

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ int _PyTestCapi_Init_Codec(PyObject *module);
5858
int _PyTestCapi_Init_Immortal(PyObject *module);
5959
int _PyTestCapi_Init_GC(PyObject *module);
6060
int _PyTestCapi_Init_Sys(PyObject *module);
61+
int _PyTestCapi_Init_Hash(PyObject *module);
6162

6263
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
6364
int _PyTestCapi_Init_HeaptypeRelative(PyObject *module);

Modules/_testcapimodule.c

+3
Original file line numberDiff line numberDiff line change
@@ -3995,6 +3995,9 @@ PyInit__testcapi(void)
39953995
if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) {
39963996
return NULL;
39973997
}
3998+
if (_PyTestCapi_Init_Hash(m) < 0) {
3999+
return NULL;
4000+
}
39984001

39994002
PyState_AddModule(m, &_testcapimodule);
40004003
return m;

PCbuild/_testcapi.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<ClCompile Include="..\Modules\_testcapi\file.c" />
125125
<ClCompile Include="..\Modules\_testcapi\codec.c" />
126126
<ClCompile Include="..\Modules\_testcapi\sys.c" />
127+
<ClCompile Include="..\Modules\_testcapi\hash.c" />
127128
<ClCompile Include="..\Modules\_testcapi\immortal.c" />
128129
<ClCompile Include="..\Modules\_testcapi\gc.c" />
129130
</ItemGroup>

PCbuild/_testcapi.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@
102102
<ClCompile Include="..\Modules\_testcapi\sys.c">
103103
<Filter>Source Files</Filter>
104104
</ClCompile>
105+
<ClCompile Include="..\Modules\_testcapi\hash.c">
106+
<Filter>Source Files</Filter>
107+
</ClCompile>
105108
<ClCompile Include="..\Modules\_testcapi\gc.c">
106109
<Filter>Source Files</Filter>
107110
</ClCompile>

Python/pyhash.c

+40-18
Original file line numberDiff line numberDiff line change
@@ -86,49 +86,71 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};
8686
Py_hash_t _Py_HashPointer(const void *);
8787

8888
Py_hash_t
89-
_Py_HashDouble(PyObject *inst, double v)
89+
PyHash_Double(double v)
9090
{
91-
int e, sign;
92-
double m;
93-
Py_uhash_t x, y;
94-
9591
if (!Py_IS_FINITE(v)) {
96-
if (Py_IS_INFINITY(v))
92+
if (Py_IS_INFINITY(v)) {
9793
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
98-
else
99-
return _Py_HashPointer(inst);
94+
}
95+
else {
96+
assert(Py_IS_NAN(v));
97+
return -1;
98+
}
10099
}
101100

102-
m = frexp(v, &e);
103-
104-
sign = 1;
101+
int e;
102+
double m = frexp(v, &e);
103+
int sign;
105104
if (m < 0) {
106105
sign = -1;
107106
m = -m;
108107
}
108+
else {
109+
sign = 1;
110+
}
109111

110112
/* process 28 bits at a time; this should work well both for binary
111113
and hexadecimal floating point. */
112-
x = 0;
114+
Py_uhash_t x = 0;
113115
while (m) {
114116
x = ((x << 28) & _PyHASH_MODULUS) | x >> (_PyHASH_BITS - 28);
115117
m *= 268435456.0; /* 2**28 */
116118
e -= 28;
117-
y = (Py_uhash_t)m; /* pull out integer part */
119+
120+
Py_uhash_t y = (Py_uhash_t)m; /* pull out integer part */
118121
m -= y;
119122
x += y;
120-
if (x >= _PyHASH_MODULUS)
123+
if (x >= _PyHASH_MODULUS) {
121124
x -= _PyHASH_MODULUS;
125+
}
122126
}
123127

124128
/* adjust for the exponent; first reduce it modulo _PyHASH_BITS */
125-
e = e >= 0 ? e % _PyHASH_BITS : _PyHASH_BITS-1-((-1-e) % _PyHASH_BITS);
129+
if (e >= 0) {
130+
e = e % _PyHASH_BITS;
131+
}
132+
else {
133+
e = _PyHASH_BITS - 1 - ((-1 - e) % _PyHASH_BITS);
134+
}
126135
x = ((x << e) & _PyHASH_MODULUS) | x >> (_PyHASH_BITS - e);
127136

128137
x = x * sign;
129-
if (x == (Py_uhash_t)-1)
130-
x = (Py_uhash_t)-2;
131-
return (Py_hash_t)x;
138+
139+
Py_hash_t result = (Py_hash_t)x;
140+
if (result == -1) {
141+
result = -2;
142+
}
143+
return (Py_hash_t)result;
144+
}
145+
146+
Py_hash_t
147+
_Py_HashDouble(PyObject *inst, double v)
148+
{
149+
Py_hash_t hash = PyHash_Double(v);
150+
if (hash == -1) {
151+
return _Py_HashPointer(inst);
152+
}
153+
return hash;
132154
}
133155

134156
Py_hash_t

0 commit comments

Comments
 (0)