putil: Switch to new Python 3.13+ public long APIs for BitArray

This commit is contained in:
rdb 2025-12-31 20:06:03 +01:00
parent 02bd38820e
commit b806035c40
2 changed files with 53 additions and 0 deletions

View File

@ -20,6 +20,29 @@
*/
void Extension<BitArray>::
__init__(PyObject *init_value) {
#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14
BitArray::WordType word = 0;
Py_ssize_t result = PyLong_AsNativeBytes(init_value, &word, sizeof(word), Py_ASNATIVEBYTES_LITTLE_ENDIAN | Py_ASNATIVEBYTES_UNSIGNED_BUFFER | Py_ASNATIVEBYTES_REJECT_NEGATIVE);
if (result < 0) {
return;
}
if ((size_t)result <= sizeof(BitArray::WordType)) {
// It fit in a single word
_this->_array.clear();
if (word != 0) {
_this->_array.push_back(word);
}
} else {
// Requires multiple words.
size_t num_words = ((size_t)result + sizeof(BitArray::WordType) - 1) / sizeof(BitArray::WordType);
_this->_array.resize(num_words);
if (PyLong_AsNativeBytes(init_value, &_this->_array[0], num_words * sizeof(BitArray::WordType), Py_ASNATIVEBYTES_LITTLE_ENDIAN | Py_ASNATIVEBYTES_UNSIGNED_BUFFER | Py_ASNATIVEBYTES_REJECT_NEGATIVE) <= 0) {
return;
}
}
#else
if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) {
PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer");
return;
@ -38,6 +61,7 @@ __init__(PyObject *init_value) {
#endif
);
}
#endif
}
/**
@ -55,19 +79,34 @@ __getstate__() const {
}
if (_this->_highest_bits == 0) {
#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14
return PyLong_FromUnsignedNativeBytes(
&_this->_array[0], _this->_array.size() * sizeof(BitArray::WordType),
Py_ASNATIVEBYTES_LITTLE_ENDIAN);
#else
return _PyLong_FromByteArray(
(const unsigned char *)&_this->_array[0],
_this->_array.size() * sizeof(BitArray::WordType),
1, 0);
#endif
} else {
// This is an infinite array, so we invert it to make it a finite array and
// store it as an inverted long.
BitArray copy(*_this);
copy.invert_in_place();
#if PY_VERSION_HEX >= 0x030e0000 // Python 3.14
PyObject *state = PyLong_FromUnsignedNativeBytes(
&copy._array[0], copy._array.size() * sizeof(BitArray::WordType),
Py_ASNATIVEBYTES_LITTLE_ENDIAN);
#else
PyObject *state = _PyLong_FromByteArray(
(const unsigned char *)&copy._array[0],
copy._array.size() * sizeof(BitArray::WordType),
1, 0);
#endif
if (state == nullptr) {
return nullptr;
}
PyObject *inverted = PyNumber_Invert(state);
Py_DECREF(state);
return inverted;

View File

@ -11,6 +11,20 @@ def test_bitarray_constructor():
assert BitArray().is_zero()
assert BitArray(0).is_zero()
one = BitArray(1)
assert not one.is_zero()
assert one.get_lowest_on_bit() == 0
assert one.get_highest_on_bit() == 0
assert one.get_lowest_off_bit() == 1
assert one.get_highest_off_bit() == -1
two = BitArray(2)
assert not two.is_zero()
assert two.get_lowest_on_bit() == 1
assert two.get_highest_on_bit() == 1
assert two.get_lowest_off_bit() == 0
assert two.get_highest_off_bit() == -1
ba = BitArray(0x10000000000000000000000000)
assert ba.get_lowest_on_bit() == 100
assert ba.get_highest_on_bit() == 100