You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider restoring _PyLong_New() function as public #111415
Clearly, we want a better C-API e.g. for import/export ints, like mpz_import/export (probably, #102471 is the right metaissue here), but I don't see good reasons to hide this function now, without any alternatives.
I don't think that accessing directly PyLongObject members and having a public "PyLong_New" API to create an uninitialized integer is a good pattern: see capi-workgroup/problems#56
I would prefer to have a generic API to create a Python int object from any format, with a convenient, but generic enough, "import" API.
I don't think that accessing directly PyLongObject members and having a public "PyLong_New" API to create an uninitialized integer is a good pattern
No, that's not what was proposed. Sure, I would prefer a better alternative: mpz_export/import-like pair + functions to get/set the sign of int and to get the length of an "array of digits". Something like #102471. (see also @casevhcomment)
The point is: we shouldn't hide _PyLong_New() without introducing an alternative in 3.13.
This function calls also _PyLong_SetSignAndDigitCount(), not only _PyLong_New().
PS: In principle, I think some parts like _PyLong_IsNegative() and _PyLong_DigitCount() could be exposed as public (or as an unstable API at least) right now, but this doesn't make too much sense without PyInt_Import/Export() functions...
I don't think that accessing directly PyLongObject members and having a public "PyLong_New" API to create an uninitialized integer is a good pattern: see capi-workgroup/problems#56
@vstinner, do you think that mpz_limbs_read/write-like API to access unsigned value of "big enough" PyLongObject as an array of "digits" is an example of this bad pattern or it does make sense and could be discussed somewhere?
With this kind of API (probably much less complex than PyInt_Import/Export) on the CPython side - Sage/gmpy2/python-flint/etc could do fast conversion to/from PyLong's using mpz_import/export functions.
@vstinner, do you think that mpz_limbs_read/write-like API to access unsigned value of "big enough" PyLongObject as an array of "digits" is an example of this bad pattern or it does make sense and could be discussed somewhere?
There is a minor issue that also impacts the current code so it doesn't impact the proposed design.
The current and example code both assume that digit is the same size (or smaller) than a long. This may not be true in the future if the bits per digit changes to 45 or 60 or something else. I would use PyLong_AsLongAndOverflow and check overflow to check if it is too large. This mostly a Windows issue. Note: checking overflow is significantly faster than raising an exception. The ...AndOverflow functions are used extensively in gmpy2.
Is there a precise definition of the behavior of PyLong_ReallocDigits? Does is always return a new object? If so, there would be a new object created if size was too large and another new object if the number is negative. I'm concerned about but I can't think of a viable solution that only uses public interfaces.
Would something like PyLong_FromSignSizeArray work? Array is an array of digits created by PyMem_Malloc(size * sizeof(digit)). Array would be filled by mpz_export. Then PyLong_FromSignSizeArray(mpz_sgn(obj->z) < 0, count, Array)would create the actualPyLong`.
checking overflow is significantly faster than raising an exception.
I was thinking about using PyLong_AsLongAndOverflow(), but I'm not sure if this will be more efficient: I assume the PyLong_AsLong() doesn't raise an exception here.
Is there a precise definition of the behavior of PyLong_ReallocDigits?
Thanks for asking, that question actually changed design:) (Above post was updated.)
PyLong_ReallocDigits(obj, size) with obj==NULL allocates new integer objects with size digits and returns it, while for existing obj - it just change the space, allocated for the absolute value of to size digits.
Would something like PyLong_FromSignSizeArray work?
Hmm, maybe it's even better. IIUIC, this solution will also avoid creation of a temporary array.
But in above code we could overestimate size.
Then PyLong_FromSignSizeArray(mpz_sgn(obj->z) < 0, count, Array)would create the actualPyLong`.
We also might want to call PyMem_Realloc() on the Array before.
I was thinking about using PyLong_AsLongAndOverflow(), but I'm not sure if this will be more efficient: I assume the PyLong_AsLong() doesn't raise an exception here.
If the number of bits per digit is greater than 32, then digit will be a long long. So any number between 33 and 64 bits will still have len equal to 1 but it won't fit into a long on Windows. Compact integer values are defined as Py_ssize_t so they also exceed the size of long on Windows. We still need to address this for gmpy2.
Would something like PyLong_FromSignSizeArray work?
Hmm, maybe it's even better. IIUIC, this solution will also avoid creation of a temporary array.
But in above code we could overestimate size.
Then PyLong_FromSignSizeArray(mpz_sgn(obj->z) < 0, count, Array)would create the actualPyLong`.
We also might want to call PyMem_Realloc() on the Array before.
Is the memory consumed by an extra digit an issue? gmpy2 has ensured that size was valid but has never reallocated the memory.
We would always run the equivalent of long_normalize() before returning the array of digits.
This sounds like an implementation detail. But hardly this will be changed.
PS: Actually, we already have (public in fact!) function _PyLong_FromDigits(). @casevh, I think it works exactly as in your proposal (for writing). Edit: no (it uses memcpy).
Activity
pythongh-111415: restoring _PyLong_New() as public
skirpichev commentedon Oct 28, 2023
CC @vstinner
vstinner commentedon Oct 28, 2023
GMP import and export functions: https://gmplib.org/manual/Integer-Import-and-Export
vstinner commentedon Oct 28, 2023
cc @serhiy-storchaka
vstinner commentedon Oct 30, 2023
I don't think that accessing directly PyLongObject members and having a public "PyLong_New" API to create an uninitialized integer is a good pattern: see capi-workgroup/problems#56
I would prefer to have a generic API to create a Python int object from any format, with a convenient, but generic enough, "import" API.
This function calls also
_PyLong_SetSignAndDigitCount(), not only_PyLong_New().This function calls also
Py_SET_SIZE(L, -pylong_size)to sets the integer sign (make it negative).The problem is to have a generic but efficient API. A generic API would be to:
The problem is to avoid (1) and (3) steps.
skirpichev commentedon Oct 30, 2023
No, that's not what was proposed. Sure, I would prefer a better alternative: mpz_export/import-like pair + functions to get/set the sign of int and to get the length of an "array of digits". Something like #102471. (see also @casevh comment)
The point is: we shouldn't hide
_PyLong_New()without introducing an alternative in 3.13.Yes.
GMPy_MPZ_From_PyLong()andGMPy_PyLong_From_MPZ()are typical consumers of this "_API".PS: In principle, I think some parts like
_PyLong_IsNegative()and_PyLong_DigitCount()could be exposed as public (or as an unstable API at least) right now, but this doesn't make too much sense withoutPyInt_Import/Export()functions...vstinner commentedon Nov 15, 2023
The
_PyLong_New()function was restored by #112115Should others functions such as
_PyLong_SetSignAndDigitCount(),_PyLong_IsNegative()and_PyLong_DigitCount()be restored as well?skirpichev commentedon Nov 16, 2023
They are copied in the gmpy2, yes. (See https://github.com/aleaxit/gmpy/blob/1edb0369690cce69ec5d90964eaead31a57e7a9b/src/gmpy2_convert.h#L135-L155) And to sage: sagemath/sage@dc71bd0
skirpichev commentedon Mar 10, 2024
@vstinner, do you think that mpz_limbs_read/write-like API to access unsigned value of "big enough" PyLongObject as an array of "digits" is an example of this bad pattern or it does make sense and could be discussed somewhere?
With this kind of API (probably much less complex than PyInt_Import/Export) on the CPython side - Sage/gmpy2/python-flint/etc could do fast conversion to/from PyLong's using mpz_import/export functions.
vstinner commentedon Mar 10, 2024
Does the new PyLong_FromNativeBytes() API fit your needs? https://docs.python.org/dev/c-api/long.html#c.PyLong_FromNativeBytes
skirpichev commentedon Mar 10, 2024
It will be a performance regression. In the gmpy2 master:
With PyLong_FromNativeBytes:
diff wrt gmpy2 master
vstinner commentedon Mar 10, 2024
Oh, that's bad. Thank you very much for testing! Which API do you need?
skirpichev commentedon Mar 11, 2024
I think that mpz_limbs_read/write-like API could look like this.
Reading (see current code):
Writing (see current code):
casevh commentedon Mar 11, 2024
I was working on a response but I'll comment on @skirpichev proposal instead.
There is a minor issue that also impacts the current code so it doesn't impact the proposed design.
The current and example code both assume that
digitis the same size (or smaller) than along. This may not be true in the future if the bits per digit changes to 45 or 60 or something else. I would usePyLong_AsLongAndOverflowand checkoverflowto check if it is too large. This mostly a Windows issue. Note: checkingoverflowis significantly faster than raising an exception. The...AndOverflowfunctions are used extensively ingmpy2.Is there a precise definition of the behavior of
PyLong_ReallocDigits? Does is always return a new object? If so, there would be a new object created ifsizewas too large and another new object if the number is negative. I'm concerned about but I can't think of a viable solution that only uses public interfaces.Would something like
PyLong_FromSignSizeArraywork?Arrayis an array of digits created byPyMem_Malloc(size * sizeof(digit)).Arraywould be filled bympz_export. ThenPyLong_FromSignSizeArray(mpz_sgn(obj->z) < 0, count, Array)would create the actualPyLong`.skirpichev commentedon Mar 11, 2024
I was thinking about using
PyLong_AsLongAndOverflow(), but I'm not sure if this will be more efficient: I assume thePyLong_AsLong()doesn't raise an exception here.Thanks for asking, that question actually changed design:) (Above post was updated.)
PyLong_ReallocDigits(obj, size)withobj==NULLallocates new integer objects withsizedigits and returns it, while for existingobj- it just change the space, allocated for the absolute value of tosizedigits.Hmm, maybe it's even better. IIUIC, this solution will also avoid creation of a temporary array.
But in above code we could overestimate
size.We also might want to call
PyMem_Realloc()on the Array before.casevh commentedon Mar 11, 2024
If the number of bits per digit is greater than 32, then
digitwill be along long. So any number between 33 and 64 bits will still havelenequal to 1 but it won't fit into alongon Windows. Compact integer values are defined asPy_ssize_tso they also exceed the size oflongon Windows. We still need to address this for gmpy2.Is the memory consumed by an extra
digitan issue? gmpy2 has ensured thatsizewas valid but has never reallocated the memory.skirpichev commentedon Mar 12, 2024
Maybe not. But CPython at least run long_normalize() in such cases.
casevh commentedon Mar 12, 2024
We would always run the equivalent of long_normalize() before returning the array of digits.
skirpichev commentedon Mar 12, 2024
This sounds like an implementation detail. But hardly this will be changed.
PS: Actually, we already have (public in fact!) function
_PyLong_FromDigits(). @casevh, I think it works exactly as in your proposal (for writing). Edit: no (it uses memcpy).vstinner commentedon Jul 3, 2024
I wrote a PR adding an import-export API for Python int objects: #121339
skirpichev commentedon Jul 4, 2024
I'm closing this as a duplicate of #102471