Skip to content

Commit 3b6ab5f

Browse files
committed
gh-111545: Add PyHash_Double() function
* Cleanup PyHash_Double() implementation based _Py_HashDouble(): * Move variable declaration to their first assignment. * Add braces (PEP 7). * Cast result to signed Py_hash_t before the final "== -1" test, to reduce the number of casts. * Add an assertion on Py_IS_NAN(v) in the only code path which can return -1. * Add tests: Modules/_testcapi/hash.c and Lib/test/test_capi/test_hash.py.
1 parent 62802b6 commit 3b6ab5f

File tree

13 files changed

+155
-19
lines changed

13 files changed

+155
-19
lines changed

Doc/c-api/hash.rst

Lines changed: 12 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 = sys.hash_info.inf
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+
def python_hash_int(x):
23+
negative = (x < 0)
24+
x = abs(x) % PyHASH_MODULUS
25+
if negative:
26+
x = -x
27+
if x == -1:
28+
x = -2
29+
return x
30+
31+
integers = [
32+
*range(1, 30),
33+
2**30 - 1,
34+
2 ** 233,
35+
int(sys.float_info.max),
36+
]
37+
integers.extend([-x for x in integers])
38+
integers.append(0)
39+
40+
for x in integers:
41+
self.assertEqual(hash_double(float(x)), python_hash_int(x), x)
42+
43+
# test non-finite values
44+
self.assertEqual(hash_double(float('inf')), PyHASH_INF)
45+
self.assertEqual(hash_double(float('-inf')), -PyHASH_INF)
46+
self.assertEqual(hash_double(float('nan')), -1)
47+
48+
# special values: compare with Python hash() function
49+
def python_hash_double(x):
50+
return hash(x)
51+
52+
special_values = (
53+
sys.float_info.max,
54+
sys.float_info.min,
55+
sys.float_info.epsilon,
56+
math.nextafter(0.0, 1.0),
57+
)
58+
for x in special_values:
59+
with self.subTest(x=x):
60+
self.assertEqual(hash_double(x), python_hash_double(x))
61+
self.assertEqual(hash_double(-x), python_hash_double(-x))
Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 25 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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;

0 commit comments

Comments
 (0)