Skip to content

Commit

Permalink
gh-120389: Add PyLong_FromInt64() and PyLong_ToInt64()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jun 19, 2024
1 parent ace2045 commit 93e27a1
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 32 deletions.
4 changes: 4 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 ``<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`

(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
89 changes: 59 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(42)), 42)
self.assertEqual(long_asint(Index(42)), 42)
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)

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

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

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

self.assertEqual(asunsignedlong(IntSubclass(42)), 42)
self.assertRaises(TypeError, asunsignedlong, Index(42))
self.assertRaises(TypeError, asunsignedlong, MyIndexAndInt())
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)

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)
def test_long_asunsignedlong(self):
# Test PyLong_AsUnsignedLong() and PyLong_FromUnsignedLong()
asunsignedlong = _testlimitedcapi.pylong_asunsignedlong
from _testcapi import ULONG_MAX
self.check_long_asunsignedint(asunsignedlong, ULONG_MAX)

def test_long_asunsignedlongmask(self):
# Test PyLong_AsUnsignedLongMask()
Expand Down Expand Up @@ -737,6 +743,29 @@ 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_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_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)

if __name__ == "__main__":
unittest.main()
4 changes: 4 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 ``<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.
8 changes: 8 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2510,3 +2510,11 @@

[function.Py_TYPE]
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));

if (PyModule_AddIntMacro(m, Py_single_input)) {
return NULL;
Expand Down
54 changes: 52 additions & 2 deletions Modules/_testlimitedcapi/long.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "pyconfig.h" // Py_GIL_DISABLED
#ifndef Py_GIL_DISABLED
// Need limited C API 3.13 to test PyLong_AsInt()
# define Py_LIMITED_API 0x030d0000
// Need limited C API 3.14 to test PyLong_AsInt64()
# define Py_LIMITED_API 0x030e0000
#endif

#include "parts.h"
Expand Down Expand Up @@ -758,6 +758,52 @@ pylong_aspid(PyObject *module, PyObject *arg)
}


static PyObject *
pylong_toint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int32_t value;
if (PyLong_ToInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt32(value);
}

static PyObject *
pylong_touint32(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint32_t value;
if (PyLong_ToUInt32(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt32(value);
}


static PyObject *
pylong_toint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
int64_t value;
if (PyLong_ToInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromInt64(value);
}

static PyObject *
pylong_touint64(PyObject *module, PyObject *arg)
{
NULLABLE(arg);
uint64_t value;
if (PyLong_ToUInt64(arg, &value) < 0) {
return NULL;
}
return PyLong_FromUInt64(value);
}


static PyMethodDef test_methods[] = {
_TESTLIMITEDCAPI_TEST_LONG_AND_OVERFLOW_METHODDEF
_TESTLIMITEDCAPI_TEST_LONG_API_METHODDEF
Expand Down Expand Up @@ -785,6 +831,10 @@ static PyMethodDef test_methods[] = {
{"pylong_asdouble", pylong_asdouble, METH_O},
{"pylong_asvoidptr", pylong_asvoidptr, METH_O},
{"pylong_aspid", pylong_aspid, METH_O},
{"pylong_toint32", pylong_toint32, METH_O},
{"pylong_touint32", pylong_touint32, METH_O},
{"pylong_toint64", pylong_toint64, METH_O},
{"pylong_touint64", pylong_touint64, METH_O},
{NULL},
};

Expand Down
81 changes: 81 additions & 0 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6681,3 +6681,84 @@ Py_ssize_t
PyUnstable_Long_CompactValue(const PyLongObject* op) {
return _PyLong_CompactValue(op);
}

PyObject* PyLong_FromInt32(int32_t value)
{ return PyLong_FromNativeBytes(&value, sizeof(value), -1); }

PyObject* PyLong_FromUInt32(uint32_t value)
{ return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); }

PyObject* PyLong_FromInt64(int64_t value)
{ return PyLong_FromNativeBytes(&value, sizeof(value), -1); }

PyObject* PyLong_FromUInt64(uint64_t value)
{ return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); }

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;
}

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;
}

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;
}

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;
}
Loading

0 comments on commit 93e27a1

Please sign in to comment.