Skip to content

Commit 26fc990

Browse files
committed
pythongh-111545: Add PyHash_Double() function
* Add again _PyHASH_NAN constant. * _Py_HashDouble(NULL, value) now returns _PyHASH_NAN. * Add tests: Modules/_testcapi/hash.c and Lib/test/test_capi/test_hash.py.
1 parent d4f83e1 commit 26fc990

File tree

10 files changed

+131
-5
lines changed

10 files changed

+131
-5
lines changed

Doc/c-api/hash.rst

+24
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,8 +45,28 @@ 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 PyHash_Double(double value, PyObject *obj)
52+
53+
Hash a C double number.
54+
55+
If *value* is not-a-number (NaN):
56+
57+
* If *obj* is not ``NULL``, return the hash of the *obj* pointer.
58+
* Otherwise, return :data:`sys.hash_info.nan <sys.hash_info>` (``0``).
59+
60+
The function cannot fail: it cannot return ``-1``.
61+
62+
.. versionadded:: 3.13
63+
64+
4465
.. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void)
4566
4667
Get the hash function definition.
4768
69+
.. seealso::
70+
:pep:`456` "Secure and interchangeable hash algorithm".
71+
4872
.. versionadded:: 3.4

Doc/library/sys.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,13 @@ always available.
10341034

10351035
.. attribute:: hash_info.nan
10361036

1037-
(This attribute is no longer used)
1037+
The hash value returned for not-a-number (NaN).
1038+
1039+
This hash value is only used by the :c:func:`Py_HashDouble` C function
1040+
when the *obj* argument is ``NULL``.
1041+
1042+
.. versionchanged:: 3.10
1043+
This hash value is no longer used to hash numbers in Python.
10381044

10391045
.. attribute:: hash_info.imag
10401046

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, PyObject *obj);

Include/internal/pycore_pyhash.h

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void*, Py_ssize_t);
3232

3333
#define _PyHASH_MODULUS (((size_t)1 << _PyHASH_BITS) - 1)
3434
#define _PyHASH_INF 314159
35+
#define _PyHASH_NAN 0
3536
#define _PyHASH_IMAG _PyHASH_MULTIPLIER
3637

3738
/* Hash secret

Lib/test/test_capi/test_hash.py

+56
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import math
12
import sys
23
import unittest
34
from test.support import import_helper
45
_testcapi = import_helper.import_module('_testcapi')
56

67

8+
NULL = None
79
SIZEOF_PY_HASH_T = _testcapi.SIZEOF_VOID_P
810

911

@@ -31,3 +33,57 @@ def test_hash_getfuncdef(self):
3133
self.assertEqual(func_def.name, hash_info.algorithm)
3234
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
3335
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
36+
37+
def test_hash_double(self):
38+
# Test PyHash_Double()
39+
hash_double = _testcapi.hash_double
40+
marker = object()
41+
marker_hash = hash(marker)
42+
43+
# test integers
44+
def python_hash_int(x):
45+
self.assertIsInstance(x, int)
46+
return hash(x)
47+
48+
integers = [
49+
*range(1, 30),
50+
2**30 - 1,
51+
2 ** 233,
52+
int(sys.float_info.max),
53+
]
54+
integers.extend([-x for x in integers])
55+
integers.append(0)
56+
57+
for x in integers:
58+
for obj in (NULL, marker):
59+
with self.subTest(x=x, obj=obj):
60+
self.assertEqual(hash_double(float(x), obj),
61+
python_hash_int(x))
62+
63+
# test +inf and -inf
64+
for obj in (NULL, marker):
65+
with self.subTest(obj=obj):
66+
self.assertEqual(hash_double(float('inf')), sys.hash_info.inf)
67+
self.assertEqual(hash_double(float('-inf')), -sys.hash_info.inf)
68+
69+
# test not-a-number (NaN)
70+
self.assertEqual(hash_double(float('nan'), marker), marker_hash)
71+
self.assertEqual(hash_double(float('nan'), NULL), sys.hash_info.nan)
72+
73+
# special float values: compare with Python hash() function
74+
def python_hash_double(x):
75+
return hash(x)
76+
77+
special_values = (
78+
math.nextafter(0.0, 1.0), # smallest positive subnormal number
79+
sys.float_info.min, # smallest positive normal number
80+
sys.float_info.epsilon,
81+
sys.float_info.max, # largest positive finite number
82+
)
83+
for x in special_values:
84+
for obj in (NULL, marker):
85+
with self.subTest(x=x, obj=obj):
86+
self.assertEqual(hash_double(x, obj),
87+
python_hash_double(x))
88+
self.assertEqual(hash_double(-x, obj),
89+
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/_testcapi/hash.c

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

4+
45
static PyObject *
56
hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
67
{
@@ -44,8 +45,26 @@ hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
4445
return result;
4546
}
4647

48+
49+
static PyObject *
50+
hash_double(PyObject *Py_UNUSED(module), PyObject *args)
51+
{
52+
double value;
53+
PyObject *obj = NULL;
54+
if (!PyArg_ParseTuple(args, "d|O", &value, &obj)) {
55+
return NULL;
56+
}
57+
NULLABLE(obj);
58+
Py_hash_t hash = PyHash_Double(value, obj);
59+
assert(hash != -1);
60+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
61+
return PyLong_FromLongLong(hash);
62+
}
63+
64+
4765
static PyMethodDef test_methods[] = {
4866
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
67+
{"hash_double", hash_double, METH_VARARGS},
4968
{NULL},
5069
};
5170

Python/pyhash.c

+16-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ 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, PyObject *obj)
9090
{
9191
int e, sign;
9292
double m;
@@ -95,8 +95,15 @@ _Py_HashDouble(PyObject *inst, double v)
9595
if (!Py_IS_FINITE(v)) {
9696
if (Py_IS_INFINITY(v))
9797
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
98-
else
99-
return _Py_HashPointer(inst);
98+
else {
99+
assert(Py_IS_NAN(v));
100+
if (obj != NULL) {
101+
return _Py_HashPointer(obj);
102+
}
103+
else {
104+
return _PyHASH_NAN;
105+
}
106+
}
100107
}
101108

102109
m = frexp(v, &e);
@@ -131,6 +138,12 @@ _Py_HashDouble(PyObject *inst, double v)
131138
return (Py_hash_t)x;
132139
}
133140

141+
Py_hash_t
142+
_Py_HashDouble(PyObject *obj, double v)
143+
{
144+
return PyHash_Double(v, obj);
145+
}
146+
134147
Py_hash_t
135148
_Py_HashPointerRaw(const void *p)
136149
{

Python/sysmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1497,7 +1497,7 @@ get_hash_info(PyThreadState *tstate)
14971497
PyStructSequence_SET_ITEM(hash_info, field++,
14981498
PyLong_FromLong(_PyHASH_INF));
14991499
PyStructSequence_SET_ITEM(hash_info, field++,
1500-
PyLong_FromLong(0)); // This is no longer used
1500+
PyLong_FromLong(_PyHASH_NAN));
15011501
PyStructSequence_SET_ITEM(hash_info, field++,
15021502
PyLong_FromLong(_PyHASH_IMAG));
15031503
PyStructSequence_SET_ITEM(hash_info, field++,

0 commit comments

Comments
 (0)