In Python2's C API there was long PyInt_AS_LONG() function which allowed a very quick creation of a C long from a Python2 int.
Is there a quick way to check, using Python3 C API, that an int (which is actually an arbitrary precision integer) fits into C long?
One can call PyLong_AsLong(), but this appears to do unnecessary work. There ought to be a function to get the size of the memory block used to store the number, but I can't find anything in the docs.
This has to be done in C, so I can't directly use Python arithmetic comparison. Although I can, in principle, call PyObject_RichCompare(), it looks like an overkill.
PyLong_AsLong code can be found at [GitHub]: python/cpython - (v3.10.13) cpython/Objects/longobject.c#453 and (as @jasonharper pointed out in a comment) uses PyLong_AsLongAndOverflow (which is right above it).
I created a test program.
_mod00.c:
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#define MOD_NAME "_mod00"
static PyObject* PyTestAsLongAndOverflow(PyObject *self, PyObject *iObj)
{
int of;
if (!PyLong_CheckExact(iObj)) {
Py_RETURN_FALSE;
}
PyLong_AsLongAndOverflow(iObj, &of);
if (of) {
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyObject* PyTestAsLong(PyObject *self, PyObject *iObj)
{
if (!PyLong_CheckExact(iObj)) {
Py_RETURN_FALSE;
}
PyLong_AsLong(iObj);
if (PyErr_Occurred()) {
PyErr_Clear();
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyObject* PyTestParseTuple(PyObject *self, PyObject *args)
{
long l = 0;
if (!PyArg_ParseTuple(args, "l", &l)) {
PyErr_Clear();
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyMethodDef moduleMethods[] = {
{"test_as_long_and_overflow", (PyCFunction)PyTestAsLongAndOverflow, METH_O, NULL},
{"test_as_long", (PyCFunction)PyTestAsLong, METH_O, NULL},
{"test_parse_tuple", (PyCFunction)PyTestParseTuple, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}, // Sentinel
};
static struct PyModuleDef moduleDef = {
PyModuleDef_HEAD_INIT, MOD_NAME, NULL, -1, moduleMethods
};
PyMODINIT_FUNC PyInit__mod00(void)
{
return PyModule_Create(&moduleDef);
}
code00.py:
#!/usr/bin/env python
import sys
import timeit
import _mod00 as mod
FUNCS = (
mod.test_as_long_and_overflow,
mod.test_as_long,
mod.test_parse_tuple,
)
def test_acc():
print("Test accuracy:")
values = (
None,
False,
3.141593,
"",
-1,
0,
1,
sys.maxsize,
sys.maxsize + 1,
-sys.maxsize,
-sys.maxsize - 1,
-sys.maxsize - 2,
)
for value in values:
print(" ({:s}) {:}{:s}:".format(value.__class__.__name__, value, " (hex: {:016X})".format(value) if isinstance(value, int) else ""))
for func in FUNCS:
print(" {:s}: {:}".format(func.__name__, func(value)))
def test_perf(count=10000000):
print("\nTest performance:")
values = (
sys.maxsize,
sys.maxsize + 1,
)
for value in values:
print(" ({:s}) {:}{:s}:".format(value.__class__.__name__, value, " (hex: {:016X})".format(value) if isinstance(value, int) else ""))
for func in FUNCS:
print(" {:s}: {:.3f}s".format(func.__name__, timeit.timeit(lambda: func(value), number=count)))
def main(*argv):
test_acc()
test_perf()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
[cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q076889790]> . ~/sopr.sh ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [064bit prompt]> ls code00.py _mod00.c [064bit prompt]> [064bit prompt]> gcc -fPIC -I/usr/include/python3.10 -shared -o _mod00.so _mod00.c [064bit prompt]> ls code00.py _mod00.c _mod00.so [064bit prompt]> [064bit prompt]> python ./code00.py Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] 064bit on linux Test accuracy: (NoneType) None: test_as_long_and_overflow: False test_as_long: False test_parse_tuple: False (bool) False (hex: 0000000000000000): test_as_long_and_overflow: False test_as_long: False test_parse_tuple: True (float) 3.141593: test_as_long_and_overflow: False test_as_long: False test_parse_tuple: False (str) : test_as_long_and_overflow: False test_as_long: False test_parse_tuple: False (int) -1 (hex: -000000000000001): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) 0 (hex: 0000000000000000): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) 1 (hex: 0000000000000001): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) 9223372036854775807 (hex: 7FFFFFFFFFFFFFFF): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) 9223372036854775808 (hex: 8000000000000000): test_as_long_and_overflow: False test_as_long: False test_parse_tuple: False (int) -9223372036854775807 (hex: -7FFFFFFFFFFFFFFF): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) -9223372036854775808 (hex: -8000000000000000): test_as_long_and_overflow: True test_as_long: True test_parse_tuple: True (int) -9223372036854775809 (hex: -8000000000000001): test_as_long_and_overflow: False test_as_long: False test_parse_tuple: False Test performance: (int) 9223372036854775807 (hex: 7FFFFFFFFFFFFFFF): test_as_long_and_overflow: 0.819s test_as_long: 0.828s test_parse_tuple: 1.140s (int) 9223372036854775808 (hex: 8000000000000000): test_as_long_and_overflow: 0.813s test_as_long: 1.128s test_parse_tuple: 1.587s Done.
Notes:
I also tested conversion via PyArg_ParseTuple (but that's slower due to tuple operations performed behind the scene by the runtime)
You were correct, setting exceptions adds some overhead
Values may vary depending on platform / architecture: [SO]: Maximum and minimum value of C types integers from Python (@CristiFati's answer)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With