Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 772690a

Browse filesBrowse files
committed
gh-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 772690a
Copy full SHA for 772690a

File tree

Expand file treeCollapse file tree

10 files changed

+128
-5
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+128
-5
lines changed

‎Doc/c-api/hash.rst

Copy file name to clipboardExpand all lines: Doc/c-api/hash.rst
+24Lines changed: 24 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Doc/library/sys.rst
+7-1Lines changed: 7 additions & 1 deletion
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:`PyHash_Double` 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

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.13.rst
+3Lines 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

Copy file name to clipboardExpand all lines: Include/cpython/pyhash.h
+2Lines 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, PyObject *obj);

‎Include/internal/pycore_pyhash.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_pyhash.h
+1Lines changed: 1 addition & 0 deletions
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

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_hash.py
+51Lines changed: 51 additions & 0 deletions
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,52 @@ 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+
integers = [
45+
*range(1, 30),
46+
2**30 - 1,
47+
2 ** 233,
48+
int(sys.float_info.max),
49+
]
50+
for x in integers:
51+
for obj in (NULL, marker):
52+
with self.subTest(x=x, obj=obj):
53+
self.assertEqual(hash_double(float(x), obj), hash(x))
54+
self.assertEqual(hash_double(float(-x), obj), hash(-x))
55+
56+
# test positive and negataive zeros
57+
for obj in (NULL, marker):
58+
with self.subTest(x=x, obj=obj):
59+
self.assertEqual(hash_double(float(0.0), obj), 0)
60+
self.assertEqual(hash_double(float(-0.0), obj), 0)
61+
62+
# test +inf and -inf
63+
inf = float("inf")
64+
for obj in (NULL, marker):
65+
with self.subTest(obj=obj):
66+
self.assertEqual(hash_double(inf), sys.hash_info.inf)
67+
self.assertEqual(hash_double(-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+
special_values = (
75+
math.nextafter(0.0, 1.0), # smallest positive subnormal number
76+
sys.float_info.min, # smallest positive normal number
77+
sys.float_info.epsilon,
78+
sys.float_info.max, # largest positive finite number
79+
)
80+
for x in special_values:
81+
for obj in (NULL, marker):
82+
with self.subTest(x=x, obj=obj):
83+
self.assertEqual(hash_double(x, obj), hash(x))
84+
self.assertEqual(hash_double(-x, obj), hash(-x))
+2Lines 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/_testcapi/hash.c

Copy file name to clipboardExpand all lines: Modules/_testcapi/hash.c
+21Lines changed: 21 additions & 0 deletions
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,28 @@ 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+
59+
Py_hash_t hash = PyHash_Double(value, obj);
60+
assert(hash != -1);
61+
62+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
63+
return PyLong_FromLongLong(hash);
64+
}
65+
66+
4767
static PyMethodDef test_methods[] = {
4868
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
69+
{"hash_double", hash_double, METH_VARARGS},
4970
{NULL},
5071
};
5172

‎Python/pyhash.c

Copy file name to clipboardExpand all lines: Python/pyhash.c
+16-3Lines changed: 16 additions & 3 deletions
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

Copy file name to clipboardExpand all lines: Python/sysmodule.c
+1-1Lines changed: 1 addition & 1 deletion
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.