Skip to content
Prev Previous commit
Next Next commit
Use PyLong_AsNativeBytes()
PyLong_ToUInt32() and PyLong_ToUInt64() can now use the __index__()
method if the object has the method.
  • Loading branch information
vstinner committed Jun 20, 2024
commit c239d18d738e3c0c23278bcde4938e3c70494993
30 changes: 14 additions & 16 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -381,23 +381,25 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
.. versionadded:: 3.14


.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value)

Return an unsigned C :c:expr:`uint32_t` representation of *obj*.

If the *obj* value is out of range, raise an :exc:`OverflowError`.
.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value)

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.
Similar to :c:func:`PyLong_ToInt32`, but return a signed C
:c:expr:`int64_t` representation.
Comment thread
vstinner marked this conversation as resolved.
Outdated

.. versionadded:: 3.14


.. c:function:: int PyLong_ToInt64(PyObject *obj, int64_t *value)
.. c:function:: int PyLong_ToUInt32(PyObject *obj, uint32_t *value)

Return a signed C :c:expr:`int64_t` representation of *obj*.
Return an unsigned C :c:expr:`uint32_t` representation of *obj*.

If the *obj* value is out of range, raise an :exc:`OverflowError`.
If *obj* is not an instance of :c:type:`PyLongObject`, first call its
:meth:`~object.__index__` method (if present) to convert it to a
:c:type:`PyLongObject`.

* If *obj* is negative, raise a :exc:`ValueError`.
* If the *obj* value is out of :c:expr:`uint32_t` range, raise an
:exc:`OverflowError`.

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.
Expand All @@ -407,12 +409,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.

.. c:function:: int PyLong_ToUInt64(PyObject *obj, uint64_t *value)

Return an unsigned C :c:expr:`uint64_t` representation of *obj*.

If the *obj* value is out of range, raise an :exc:`OverflowError`.

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.
Similar to :c:func:`PyLong_ToUInt32`, but return an unsigned C
:c:expr:`int64_t` representation.

.. versionadded:: 3.14

Expand Down
4 changes: 2 additions & 2 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,12 @@ New Features
:class:`int`:

* :c:func:`PyLong_FromInt32`
* :c:func:`PyLong_FromUInt32`
* :c:func:`PyLong_FromInt64`
* :c:func:`PyLong_FromUInt32`
* :c:func:`PyLong_FromUInt64`
* :c:func:`PyLong_ToInt32`
* :c:func:`PyLong_ToUInt32`
* :c:func:`PyLong_ToInt64`
* :c:func:`PyLong_ToUInt32`
* :c:func:`PyLong_ToUInt64`

(Contributed by Victor Stinner in :gh:`120389`.)
Expand Down
39 changes: 25 additions & 14 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ def check_long_asint(self, long_asint, min_val, max_val):
for value in (min_val, max_val, -1, 0, 1, 1234):
with self.subTest(value=value):
self.assertEqual(long_asint(value), value)
self.assertEqual(long_asint(IntSubclass(value)), value)
self.assertEqual(long_asint(Index(value)), value)

self.assertEqual(long_asint(IntSubclass(42)), 42)
self.assertEqual(long_asint(Index(42)), 42)
self.assertEqual(long_asint(MyIndexAndInt()), 10)

self.assertRaises(OverflowError, long_asint, min_val - 1)
Expand Down Expand Up @@ -226,17 +226,24 @@ def test_long_aslongandoverflow(self):
# CRASHES aslongandoverflow(1.0)
# CRASHES aslongandoverflow(NULL)

def check_long_asunsignedint(self, long_asuint, max_val):
def check_long_asunsignedint(self, long_asuint, max_val,
use_index=False, negative_value_error=False):
# round trip (object -> unsigned long -> object)
for value in (0, 1, 1234, max_val):
with self.subTest(value=value):
self.assertEqual(long_asuint(value), value)
if use_index:
self.assertEqual(long_asuint(Index(value)), value)

self.assertEqual(long_asuint(IntSubclass(42)), 42)
self.assertRaises(TypeError, long_asuint, Index(42))
self.assertRaises(TypeError, long_asuint, MyIndexAndInt())

self.assertRaises(OverflowError, long_asuint, -1)
if not use_index:
self.assertRaises(TypeError, long_asuint, Index(42))
self.assertRaises(TypeError, long_asuint, MyIndexAndInt())

if negative_value_error:
self.assertRaises(ValueError, long_asuint, -1)
else:
self.assertRaises(OverflowError, long_asuint, -1)
self.assertRaises(OverflowError, long_asuint, max_val + 1)
self.assertRaises(TypeError, long_asuint, 1.0)
self.assertRaises(TypeError, long_asuint, b'2')
Expand Down Expand Up @@ -749,23 +756,27 @@ def test_long_asint32(self):
from _testcapi import INT32_MIN, INT32_MAX
self.check_long_asint(to_int32, INT32_MIN, INT32_MAX)

def test_long_asuint32(self):
# Test PyLong_ToUInt32() and PyLong_FromUInt32()
to_uint32 = _testlimitedcapi.pylong_touint32
from _testcapi import UINT32_MAX
self.check_long_asunsignedint(to_uint32, UINT32_MAX)

def test_long_asint64(self):
# Test PyLong_ToInt64() and PyLong_FromInt64()
to_int64 = _testlimitedcapi.pylong_toint64
from _testcapi import INT64_MIN, INT64_MAX
self.check_long_asint(to_int64, INT64_MIN, INT64_MAX)

def test_long_asuint32(self):
# Test PyLong_ToUInt32() and PyLong_FromUInt32()
to_uint32 = _testlimitedcapi.pylong_touint32
from _testcapi import UINT32_MAX
self.check_long_asunsignedint(to_uint32, UINT32_MAX,
use_index=True,
negative_value_error=True)

def test_long_asuint64(self):
# Test PyLong_ToUInt64() and PyLong_FromUInt64()
to_uint64 = _testlimitedcapi.pylong_touint64
from _testcapi import UINT64_MAX
self.check_long_asunsignedint(to_uint64, UINT64_MAX)
self.check_long_asunsignedint(to_uint64, UINT64_MAX,
use_index=True,
negative_value_error=True)

if __name__ == "__main__":
unittest.main()
89 changes: 36 additions & 53 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6694,71 +6694,54 @@ PyObject* PyLong_FromInt64(int64_t value)
PyObject* PyLong_FromUInt64(uint64_t value)
{ return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); }

#define LONG_TO_INT(obj, value, type_name) \
do { \
int flags = Py_ASNATIVEBYTES_NATIVE_ENDIAN; \
Comment thread
vstinner marked this conversation as resolved.
Outdated
Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \
if (bytes < 0) { \
return -1; \
} \
if ((size_t)bytes > sizeof(*value)) { \
PyErr_SetString(PyExc_OverflowError, \
"Python int too large to convert to " type_name); \
return -1; \
} \
return 0; \
} while (0)

int PyLong_ToInt32(PyObject *obj, int32_t *value)
{
#if SIZEOF_INT == 4
int res = PyLong_AsInt(obj);
#elif SIZEOF_LONG == 4
long res = PyLong_AsLong(obj);
#else
# error "unknown int type"
#endif
if (res == -1 && PyErr_Occurred()) {
return -1;
}
*value = res;
return 0;
LONG_TO_INT(obj, value, "C int32_t");
}

int PyLong_ToInt64(PyObject *obj, int64_t *value)
{
#if SIZEOF_LONG == 8
long res = PyLong_AsLong(obj);
#elif SIZEOF_LONG_LONG == 8
long long res = PyLong_AsLongLong(obj);
#else
# error "unknown long type"
#endif
if (res == -1 && PyErr_Occurred()) {
return -1;
}
*value = res;
return 0;
LONG_TO_INT(obj, value, "C int64_t");
}

#define LONG_TO_UINT(obj, value, type_name) \
do { \
int flags = (Py_ASNATIVEBYTES_NATIVE_ENDIAN \
| Py_ASNATIVEBYTES_UNSIGNED_BUFFER \
| Py_ASNATIVEBYTES_REJECT_NEGATIVE); \
Py_ssize_t bytes = PyLong_AsNativeBytes(obj, value, sizeof(*value), flags); \
Comment thread
vstinner marked this conversation as resolved.
if (bytes < 0) { \
return -1; \
} \
if ((size_t)bytes > sizeof(*value)) { \
PyErr_SetString(PyExc_OverflowError, \
"Python int too large to convert to " type_name); \
return -1; \
} \
return 0; \
} while (0)

int PyLong_ToUInt32(PyObject *obj, uint32_t *value)
{
Py_BUILD_ASSERT(sizeof(unsigned long) >= sizeof(uint32_t));
unsigned long res = PyLong_AsUnsignedLong(obj);
if (res == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
}
#if SIZEOF_LONG > 4
if (res > (unsigned long)UINT32_MAX) {
PyErr_SetString(PyExc_OverflowError,
"Python int too large to convert to C uint32_t");
return -1;
}
#endif
*value = res;
return 0;
LONG_TO_UINT(obj, value, "C uint32_t");
}

int PyLong_ToUInt64(PyObject *obj, uint64_t *value)
{
#if SIZEOF_LONG == 8
unsigned long res = PyLong_AsUnsignedLong(obj);
if (res == (unsigned long)-1 && PyErr_Occurred()) {
return -1;
}
#elif SIZEOF_LONG_LONG == 8
unsigned long long res = PyLong_AsUnsignedLongLong(obj);
if (res == (unsigned long long)-1 && PyErr_Occurred()) {
return -1;
}
#else
# error "unknown long type"
#endif
*value = res;
return 0;
LONG_TO_UINT(obj, value, "C uint64_t");
}