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 46fef17

Browse filesBrowse 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 55f3cce commit 46fef17
Copy full SHA for 46fef17

File tree

Expand file treeCollapse file tree

7 files changed

+129
-18
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+129
-18
lines changed

‎Doc/c-api/hash.rst

Copy file name to clipboardExpand all lines: Doc/c-api/hash.rst
+19Lines changed: 19 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,23 @@ 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)
52+
53+
Hash a C double number.
54+
55+
Return ``-1`` if *value* is not-a-number (NaN).
56+
57+
.. versionadded:: 3.13
58+
59+
4460
.. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void)
4561
4662
Get the hash function definition.
4763
64+
.. seealso::
65+
:pep:`456` "Secure and interchangeable hash algorithm".
66+
4867
.. versionadded:: 3.4

‎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);

‎Lib/test/test_capi/test_hash.py

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_hash.py
+47Lines changed: 47 additions & 0 deletions
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
@@ -31,3 +32,49 @@ def test_hash_getfuncdef(self):
3132
self.assertEqual(func_def.name, hash_info.algorithm)
3233
self.assertEqual(func_def.hash_bits, hash_info.hash_bits)
3334
self.assertEqual(func_def.seed_bits, hash_info.seed_bits)
35+
36+
def test_hash_double(self):
37+
# Test PyHash_Double()
38+
hash_double = _testcapi.hash_double
39+
40+
# test integers
41+
def python_hash_int(x):
42+
negative = (x < 0)
43+
x = abs(x) % sys.hash_info.modulus
44+
if negative:
45+
x = -x
46+
if x == -1:
47+
x = -2
48+
return x
49+
50+
integers = [
51+
*range(1, 30),
52+
2**30 - 1,
53+
2 ** 233,
54+
int(sys.float_info.max),
55+
]
56+
integers.extend([-x for x in integers])
57+
integers.append(0)
58+
59+
for x in integers:
60+
self.assertEqual(hash_double(float(x)), python_hash_int(x), x)
61+
62+
# test non-finite values
63+
self.assertEqual(hash_double(float('inf')), sys.hash_info.inf)
64+
self.assertEqual(hash_double(float('-inf')), -sys.hash_info.inf)
65+
self.assertEqual(hash_double(float('nan')), -1)
66+
67+
# special values: compare with Python hash() function
68+
def python_hash_double(x):
69+
return hash(x)
70+
71+
special_values = (
72+
sys.float_info.max,
73+
sys.float_info.min,
74+
sys.float_info.epsilon,
75+
math.nextafter(0.0, 1.0),
76+
)
77+
for x in special_values:
78+
with self.subTest(x=x):
79+
self.assertEqual(hash_double(x), python_hash_double(x))
80+
self.assertEqual(hash_double(-x), python_hash_double(-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
+16Lines changed: 16 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,23 @@ 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+
if (!PyArg_ParseTuple(args, "d", &value)) {
54+
return NULL;
55+
}
56+
Py_hash_t hash = PyHash_Double(value);
57+
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
58+
return PyLong_FromLongLong(hash);
59+
}
60+
61+
4762
static PyMethodDef test_methods[] = {
4863
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
64+
{"hash_double", hash_double, METH_VARARGS},
4965
{NULL},
5066
};
5167

‎Python/pyhash.c

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