8000 gh-111545: Add Py_HashDouble() function by vstinner · Pull Request #112449 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-111545: Add Py_HashDouble() function #112449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-111545: Add Py_HashDouble() function
* Add again the private _PyHASH_NAN constant.
* Add tests: Modules/_testcapi/hash.c and
  Lib/test/test_capi/test_hash.py.
  • Loading branch information
vstinner committed Dec 12, 2023
commit 250c3e1e9965e4b0359d82e64cf52bf83a266ec3
26 changes: 26 additions & 0 deletions Doc/c-api/hash.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ PyHash API

See also the :c:member:`PyTypeObject.tp_hash` member.

Types
^^^^^

.. c:type:: Py_hash_t

Hash value type: signed integer.

.. versionadded:: 3.2


.. c:type:: Py_uhash_t

Hash value type: unsigned integer.
Expand Down Expand Up @@ -41,6 +45,28 @@ See also the :c:member:`PyTypeObject.tp_hash` member.
.. versionadded:: 3.4


Functions
^^^^^^^^^

.. c:function:: int Py_HashDouble(double value, Py_hash_t *result)

Hash a C double number.

* Set *\*result* to the hash and return ``1`` if *value* is finite or is
infinity.
* Set *\*result* to :data:`sys.hash_info.nan <sys.hash_info>` (``0``) and
return ``0`` if *value* is not-a-number (NaN).

*result* must not be ``NULL``.

.. note::
Only rely on the function return value to distinguish the "not-a-number"
case. *\*result* can be ``0`` if *value* is finite. For example,
``Py_HashDouble(0.0, &result)`` sets *\*result* to 0.

.. versionadded:: 3.13


.. c:function:: PyHash_FuncDef* PyHash_GetFuncDef(void)

Get the hash function definition.
Expand Down
8 changes: 7 additions & 1 deletion Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,13 @@ always available.

.. attribute:: hash_info.nan

(This attribute is no longer used)
The hash value returned for not-a-number (NaN).

This hash value is only used by the :c:func:`Py_HashDouble` C function if
the argument is not-a-number (NaN).

.. versionchanged:: 3.10
This hash value is no longer used to hash numbers in Python.

.. attribute:: hash_info.imag

Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,9 @@ New Features
* Add :c:func:`Py_HashPointer` function to hash a pointer.
(Contributed by Victor Stinner in :gh:`111545`.)

* Add :c:func:`Py_HashDouble` function to hash a C double number.
(Contributed by Victor Stinner in :gh:`111545`.)


Porting to Python 3.13
----------------------
Expand Down
2 changes: 2 additions & 0 deletions Include/cpython/pyhash.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#define _PyHASH_MODULUS (((size_t)1 << _PyHASH_BITS) - 1)
#define _PyHASH_INF 314159
#define _PyHASH_NAN 0
#define _PyHASH_IMAG _PyHASH_MULTIPLIER

/* Helpers for hash functions */
Expand All @@ -37,3 +38,4 @@ typedef struct {
PyAPI_FUNC(PyHash_FuncDef*) PyHash_GetFuncDef(void);

PyAPI_FUNC(Py_hash_t) Py_HashPointer(const void *ptr);
PyAPI_FUNC(int) Py_HashDouble(double value, Py_hash_t *result);
44 changes: 44 additions & 0 deletions Lib/test/test_capi/test_hash.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
import sys
import unittest
from test.support import import_helper
Expand Down Expand Up @@ -77,3 +78,46 @@ def python_hash_pointer(x):
# Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
self.assertEqual(hash_pointer(VOID_P_MAX), -2)

def test_hash_double(self):
# Test Py_HashDouble()
hash_double = _testcapi.hash_double

def check_number(value, expected):
self.assertEqual(hash_double(value), (1, expected))

# test some integers
integers = [
*range(1, 30),
2**30 - 1,
2 ** 233,
int(sys.float_info.max),
]
for x in integers:
with self.subTest(x=x):
check_number(float(x), hash(x))
check_number(float(-x), hash(-x))

# test positive and negative zeros
check_number(float(0.0), 0)
check_number(float(-0.0), 0)

# test +inf and -inf
inf = float("inf")
check_number(inf, sys.hash_info.inf)
check_number(-inf, -sys.hash_info.inf)

# special float values: compare with Python hash() function
special_values = (
math.nextafter(0.0, 1.0), # smallest positive subnormal number
sys.float_info.min, # smallest positive normal number
sys.float_info.epsilon,
sys.float_info.max, # largest positive finite number
)
for x in special_values:
with self.subTest(x=x):
check_number(x, hash(x))
check_number(-x, hash(-x))

# test not-a-number (NaN)
self.assertEqual(hash_double(float('nan')), (0, sys.hash_info.nan))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`Py_HashDouble` function to hash a C double number. Patch by
Victor Stinner.
29 changes: 27 additions & 2 deletions Modules/_testcapi/hash.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
#include "parts.h"
#include "util.h"


static PyObject *
long_from_hash(Py_hash_t hash)
{
assert(hash != -1);

Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
return PyLong_FromLongLong(hash);
}


static PyObject *
hash_getfuncdef(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
Expand Down Expand Up @@ -54,14 +65,28 @@ hash_pointer(PyObject *Py_UNUSED(module), PyObject *arg)
}

Py_hash_t hash = Py_HashPointer(ptr);
Py_BUILD_ASSERT(sizeof(long long) >= sizeof(hash));
return PyLong_FromLongLong(hash);
return long_from_hash(hash);
}


static PyObject *
hash_double(PyObject *Py_UNUSED(module), PyObject *args)
{
double value;
if (!PyArg_ParseTuple(args, "d", &value)) {
return NULL;
}

Py_hash_t hash;
int res = Py_HashDouble(value, &hash);
return Py_BuildValue("iN", res, long_from_hash(hash));
}


static PyMethodDef test_methods[] = {
{"hash_getfuncdef", hash_getfuncdef, METH_NOARGS},
{"hash_pointer", hash_pointer, METH_O},
{"hash_double", hash_double, METH_VARARGS},
{NULL},
};

Expand Down
32 changes: 25 additions & 7 deletions Python/pyhash.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,23 @@ static Py_ssize_t hashstats[Py_HASH_STATS_MAX + 1] = {0};

*/

Py_hash_t
_Py_HashDouble(PyObject *inst, double v)
int
Py_HashDouble(double v, Py_hash_t *result)
{
int e, sign;
double m;
Py_uhash_t x, y;

if (!Py_IS_FINITE(v)) {
if (Py_IS_INFINITY(v))
return v > 0 ? _PyHASH_INF : -_PyHASH_INF;
else
return _Py_HashPointer(inst);
if (Py_IS_INFINITY(v)) {
*result = (v > 0 ? _PyHASH_INF : -_PyHASH_INF);
return 1;
}
else {
assert(Py_IS_NAN(v));
*result = _PyHASH_NAN;
return 0;
}
}

m = frexp(v, &e);
Expand Down Expand Up @@ -126,7 +131,20 @@ _Py_HashDouble(PyObject *inst, double v)
x = x * sign;
if (x == (Py_uhash_t)-1)
x = (Py_uhash_t)-2;
return (Py_hash_t)x;
*result = (Py_hash_t)x;
return 1;
}

Py_hash_t
_Py_HashDouble(PyObject *obj, double v)
{
assert(obj != NULL);

Py_hash_t hash;
if (Py_HashDouble(v, &hash) == 0) {
hash = Py_HashPointer(obj);
}
return hash;
}

Py_hash_t
Expand Down
2 changes: 1 addition & 1 deletion Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,7 @@ get_hash_info(PyThreadState *tstate)
PyStructSequence_SET_ITEM(hash_info, field++,
PyLong_FromLong(_PyHASH_INF));
PyStructSequence_SET_ITEM(hash_info, field++,
PyLong_FromLong(0)); // This is no longer used
PyLong_FromLong(_PyHASH_NAN));
PyStructSequence_SET_ITEM(hash_info, field++,
PyLong_FromLong(_PyHASH_IMAG));
PyStructSequence_SET_ITEM(hash_info, field++,
Expand Down
0