Skip to content
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

gh-120389: Add PyLong_FromInt64() and PyLong_AsInt64() #120390

Merged
merged 12 commits into from
Aug 28, 2024
51 changes: 51 additions & 0 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,30 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
on failure.


.. c:function:: PyObject* PyLong_FromInt32(int32_t value)
PyObject* PyLong_FromInt64(int64_t value)

Return a new :c:type:`PyLongObject` object from a signed C
:c:expr:`int32_t` or :c:expr:`int64_t`, or ``NULL`` on failure.
vstinner marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 3.14


.. c:function:: PyObject* PyLong_FromUnsignedLongLong(unsigned long long v)

Return a new :c:type:`PyLongObject` object from a C :c:expr:`unsigned long long`,
or ``NULL`` on failure.


.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value)
PyObject* PyLong_FromUInt64(uint64_t value)

Return a new :c:type:`PyLongObject` object from an unsigned C
:c:expr:`uint32_t` or :c:expr:`uint64_t`, or ``NULL`` on failure.
vstinner marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 3.14


.. c:function:: PyObject* PyLong_FromDouble(double v)

Return a new :c:type:`PyLongObject` object from the integer part of *v*, or
Expand Down Expand Up @@ -337,6 +355,39 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
This function will no longer use :meth:`~object.__int__`.


.. c:function:: int PyLong_ToInt32(PyObject *obj, int32_t *value)
int PyLong_ToInt64(PyObject *obj, int64_t *value)

Return a signed C :c:expr:`int32_t` or :c:expr:`int64_t` representation of
vstinner marked this conversation as resolved.
Show resolved Hide resolved
*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.

.. versionadded:: 3.14


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

Return an unsigned C :c:expr:`uint32_t` or :c:expr:`uint64_t` representation
vstinner marked this conversation as resolved.
Show resolved Hide resolved
of *obj*.

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 range, raise an :exc:`OverflowError`.

Set *\*value* and return ``0`` on success.
Set an exception and return ``-1`` on error.

.. versionadded:: 3.14


.. c:function:: double PyLong_AsDouble(PyObject *pylong)

Return a C :c:expr:`double` representation of *pylong*. *pylong* must be
Expand Down
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
('c:type', 'size_t'),
('c:type', 'ssize_t'),
('c:type', 'time_t'),
('c:type', 'uint32_t'),
('c:type', 'uint64_t'),
('c:type', 'uintmax_t'),
('c:type', 'uintptr_t'),
Expand Down
8 changes: 8 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,20 @@ New Features

(Contributed by Victor Stinner in :gh:`119182`.)

* Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

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

(Contributed by Victor Stinner in :gh:`120389`.)

Porting to Python 3.14
----------------------

Expand Down
12 changes: 12 additions & 0 deletions Include/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLongMask(PyObject *);
PyAPI_FUNC(int) PyLong_AsInt(PyObject *);
#endif

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
PyAPI_FUNC(PyObject*) PyLong_FromInt32(int32_t value);
PyAPI_FUNC(PyObject*) PyLong_FromUInt32(uint32_t value);
PyAPI_FUNC(PyObject*) PyLong_FromInt64(int64_t value);
PyAPI_FUNC(PyObject*) PyLong_FromUInt64(uint64_t value);

PyAPI_FUNC(int) PyLong_ToInt32(PyObject *obj, int32_t *value);
PyAPI_FUNC(int) PyLong_ToUInt32(PyObject *obj, uint32_t *value);
PyAPI_FUNC(int) PyLong_ToInt64(PyObject *obj, int64_t *value);
PyAPI_FUNC(int) PyLong_ToUInt64(PyObject *obj, uint64_t *value);
#endif

PyAPI_FUNC(PyObject *) PyLong_GetInfo(void);

/* It may be useful in the future. I've added it in the PyInt -> PyLong
Expand Down
100 changes: 70 additions & 30 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,25 +185,28 @@ def test_long_asint(self):
self.assertRaises(TypeError, PyLong_AsInt, '3')
self.assertRaises(SystemError, PyLong_AsInt, NULL)

def check_long_asint(self, long_asint, min_val, max_val):
# round trip (object -> C integer -> object)
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(MyIndexAndInt()), 10)

self.assertRaises(OverflowError, long_asint, min_val - 1)
self.assertRaises(OverflowError, long_asint, max_val + 1)
self.assertRaises(TypeError, long_asint, 1.0)
self.assertRaises(TypeError, long_asint, b'2')
self.assertRaises(TypeError, long_asint, '3')
self.assertRaises(SystemError, long_asint, NULL)

def test_long_aslong(self):
# Test PyLong_AsLong() and PyLong_FromLong()
aslong = _testlimitedcapi.pylong_aslong
from _testcapi import LONG_MIN, LONG_MAX
# round trip (object -> long -> object)
for value in (LONG_MIN, LONG_MAX, -1, 0, 1, 1234):
with self.subTest(value=value):
self.assertEqual(aslong(value), value)

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

self.assertRaises(OverflowError, aslong, LONG_MIN - 1)
self.assertRaises(OverflowError, aslong, LONG_MAX + 1)
self.assertRaises(TypeError, aslong, 1.0)
self.assertRaises(TypeError, aslong, b'2')
self.assertRaises(TypeError, aslong, '3')
self.assertRaises(SystemError, aslong, NULL)
self.check_long_asint(aslong, LONG_MIN, LONG_MAX)
vstinner marked this conversation as resolved.
Show resolved Hide resolved

def test_long_aslongandoverflow(self):
# Test PyLong_AsLongAndOverflow()
Expand All @@ -223,25 +226,35 @@ def test_long_aslongandoverflow(self):
# CRASHES aslongandoverflow(1.0)
# CRASHES aslongandoverflow(NULL)

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)
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')
self.assertRaises(TypeError, long_asuint, '3')
self.assertRaises(SystemError, long_asuint, NULL)

def test_long_asunsignedlong(self):
# Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()
asunsignedlong = _testlimitedcapi.pylong_asunsignedlong
from _testcapi import ULONG_MAX
# round trip (object -> unsigned long -> object)
for value in (ULONG_MAX, 0, 1, 1234):
with self.subTest(value=value):
self.assertEqual(asunsignedlong(value), value)

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

self.assertRaises(OverflowError, asunsignedlong, -1)
self.assertRaises(OverflowError, asunsignedlong, ULONG_MAX + 1)
self.assertRaises(TypeError, asunsignedlong, 1.0)
self.assertRaises(TypeError, asunsignedlong, b'2')
self.assertRaises(TypeError, asunsignedlong, '3')
self.assertRaises(SystemError, asunsignedlong, NULL)
self.check_long_asunsignedint(asunsignedlong, ULONG_MAX)

def test_long_asunsignedlongmask(self):
# Test PyLong_AsUnsignedLongMask()
Expand Down Expand Up @@ -737,6 +750,33 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

def test_long_asint32(self):
# Test PyLong_ToInt32() and PyLong_FromInt32()
to_int32 = _testlimitedcapi.pylong_toint32
from _testcapi import INT32_MIN, INT32_MAX
self.check_long_asint(to_int32, INT32_MIN, INT32_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,
use_index=True,
negative_value_error=True)

if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Add new functions to convert C ``<stdint.h>`` numbers from/to Python
:class:`int`:

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

Patch by Victor Stinner.
16 changes: 16 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,19 @@

[function.Py_TYPE]
added = '3.14'
[function.PyLong_FromInt32]
added = '3.14'
[function.PyLong_FromUInt32]
added = '3.14'
[function.PyLong_ToInt32]
added = '3.14'
[function.PyLong_ToUInt32]
added = '3.14'
[function.PyLong_FromInt64]
added = '3.14'
[function.PyLong_FromUInt64]
added = '3.14'
[function.PyLong_ToInt64]
added = '3.14'
[function.PyLong_ToUInt64]
added = '3.14'
6 changes: 6 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4047,6 +4047,12 @@ PyInit__testcapi(void)

PyModule_AddIntConstant(m, "the_number_three", 3);
PyModule_AddIntMacro(m, Py_C_RECURSION_LIMIT);
PyModule_AddObject(m, "INT32_MIN", PyLong_FromInt32(INT32_MIN));
PyModule_AddObject(m, "INT32_MAX", PyLong_FromInt32(INT32_MAX));
PyModule_AddObject(m, "UINT32_MAX", PyLong_FromUInt32(UINT32_MAX));
PyModule_AddObject(m, "INT64_MIN", PyLong_FromInt64(INT64_MIN));
PyModule_AddObject(m, "INT64_MAX", PyLong_FromInt64(INT64_MAX));
PyModule_AddObject(m, "UINT64_MAX", PyLong_FromUInt64(UINT64_MAX));
Comment on lines +4049 to +4054
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. You can define them in the Python code as INT32_MAX = 2**31 - 1 etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to reuse <limits.h> C constants to avoid any typo.


if (PyModule_AddIntMacro(m, Py_single_input)) {
return NULL;
Expand Down
Loading
Loading