mirror of https://github.com/python/cpython
bpo-29882: Add an efficient popcount method for integers (#771)
* bpo-29882: Add an efficient popcount method for integers * Update 'sign bit' and versionadded in docs * Add entry to whatsnew document * Doc: use positive example, mention population count * Minor cleanups of the core code * Move popcount_digit closer to where it's used * Use z instead of self after conversion * Add 'absolute value' and 'population count' to docstring * Fix clinic error about missing summary line * Ensure popcount_digit is portable with 64-bit ints Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
This commit is contained in:
parent
364b5ead15
commit
8bd216dfed
|
@ -478,6 +478,27 @@ class`. In addition, it provides a few more methods:
|
||||||
|
|
||||||
.. versionadded:: 3.1
|
.. versionadded:: 3.1
|
||||||
|
|
||||||
|
.. method:: int.bit_count()
|
||||||
|
|
||||||
|
Return the number of ones in the binary representation of the absolute
|
||||||
|
value of the integer. This is also known as the population count.
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> n = 19
|
||||||
|
>>> bin(n)
|
||||||
|
'0b10011'
|
||||||
|
>>> n.bit_count()
|
||||||
|
3
|
||||||
|
>>> (-n).bit_count()
|
||||||
|
3
|
||||||
|
|
||||||
|
Equivalent to::
|
||||||
|
|
||||||
|
def bit_count(self):
|
||||||
|
return bin(self).count("1")
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
.. method:: int.to_bytes(length, byteorder, \*, signed=False)
|
.. method:: int.to_bytes(length, byteorder, \*, signed=False)
|
||||||
|
|
||||||
Return an array of bytes representing an integer.
|
Return an array of bytes representing an integer.
|
||||||
|
|
|
@ -70,6 +70,9 @@ Summary -- Release highlights
|
||||||
New Features
|
New Features
|
||||||
============
|
============
|
||||||
|
|
||||||
|
* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
|
||||||
|
number of ones in the binary expansion of a given integer, also known
|
||||||
|
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)
|
||||||
|
|
||||||
|
|
||||||
Other Language Changes
|
Other Language Changes
|
||||||
|
|
|
@ -669,7 +669,7 @@ plain ol' Python and is guaranteed to be available.
|
||||||
True
|
True
|
||||||
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
||||||
>>> len(real_tests) # objects that actually have doctests
|
>>> len(real_tests) # objects that actually have doctests
|
||||||
13
|
14
|
||||||
>>> for t in real_tests:
|
>>> for t in real_tests:
|
||||||
... print('{} {}'.format(len(t.examples), t.name))
|
... print('{} {}'.format(len(t.examples), t.name))
|
||||||
...
|
...
|
||||||
|
@ -682,6 +682,7 @@ plain ol' Python and is guaranteed to be available.
|
||||||
1 builtins.hex
|
1 builtins.hex
|
||||||
1 builtins.int
|
1 builtins.int
|
||||||
3 builtins.int.as_integer_ratio
|
3 builtins.int.as_integer_ratio
|
||||||
|
2 builtins.int.bit_count
|
||||||
2 builtins.int.bit_length
|
2 builtins.int.bit_length
|
||||||
5 builtins.memoryview.hex
|
5 builtins.memoryview.hex
|
||||||
1 builtins.oct
|
1 builtins.oct
|
||||||
|
|
|
@ -1016,6 +1016,17 @@ class LongTest(unittest.TestCase):
|
||||||
self.assertEqual((a+1).bit_length(), i+1)
|
self.assertEqual((a+1).bit_length(), i+1)
|
||||||
self.assertEqual((-a-1).bit_length(), i+1)
|
self.assertEqual((-a-1).bit_length(), i+1)
|
||||||
|
|
||||||
|
def test_bit_count(self):
|
||||||
|
for a in range(-1000, 1000):
|
||||||
|
self.assertEqual(a.bit_count(), bin(a).count("1"))
|
||||||
|
|
||||||
|
for exp in [10, 17, 63, 64, 65, 1009, 70234, 1234567]:
|
||||||
|
a = 2**exp
|
||||||
|
self.assertEqual(a.bit_count(), 1)
|
||||||
|
self.assertEqual((a - 1).bit_count(), exp)
|
||||||
|
self.assertEqual((a ^ 63).bit_count(), 7)
|
||||||
|
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
|
||||||
|
|
||||||
def test_round(self):
|
def test_round(self):
|
||||||
# check round-half-even algorithm. For round to nearest ten;
|
# check round-half-even algorithm. For round to nearest ten;
|
||||||
# rounding map is invariant under adding multiples of 20
|
# rounding map is invariant under adding multiples of 20
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :meth:`int.bit_count()`, counting the number of ones in the binary
|
||||||
|
representation of an integer. Patch by Niklas Fiekas.
|
|
@ -138,6 +138,31 @@ int_bit_length(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
return int_bit_length_impl(self);
|
return int_bit_length_impl(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(int_bit_count__doc__,
|
||||||
|
"bit_count($self, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Number of ones in the binary representation of the absolute value of self.\n"
|
||||||
|
"\n"
|
||||||
|
"Also known as the population count.\n"
|
||||||
|
"\n"
|
||||||
|
">>> bin(13)\n"
|
||||||
|
"\'0b1101\'\n"
|
||||||
|
">>> (13).bit_count()\n"
|
||||||
|
"3");
|
||||||
|
|
||||||
|
#define INT_BIT_COUNT_METHODDEF \
|
||||||
|
{"bit_count", (PyCFunction)int_bit_count, METH_NOARGS, int_bit_count__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
int_bit_count_impl(PyObject *self);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
int_bit_count(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return int_bit_count_impl(self);
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(int_as_integer_ratio__doc__,
|
PyDoc_STRVAR(int_as_integer_ratio__doc__,
|
||||||
"as_integer_ratio($self, /)\n"
|
"as_integer_ratio($self, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -308,4 +333,4 @@ skip_optional_kwonly:
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=63b8274fc784d617 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=4257cfdb155efd00 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -5304,6 +5304,75 @@ int_bit_length_impl(PyObject *self)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
popcount_digit(digit d)
|
||||||
|
{
|
||||||
|
/* 32bit SWAR popcount. */
|
||||||
|
uint32_t u = d;
|
||||||
|
u -= (u >> 1) & 0x55555555U;
|
||||||
|
u = (u & 0x33333333U) + ((u >> 2) & 0x33333333U);
|
||||||
|
u = (u + (u >> 4)) & 0x0f0f0f0fU;
|
||||||
|
return (uint32_t)(u * 0x01010101U) >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
int.bit_count
|
||||||
|
|
||||||
|
Number of ones in the binary representation of the absolute value of self.
|
||||||
|
|
||||||
|
Also known as the population count.
|
||||||
|
|
||||||
|
>>> bin(13)
|
||||||
|
'0b1101'
|
||||||
|
>>> (13).bit_count()
|
||||||
|
3
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
int_bit_count_impl(PyObject *self)
|
||||||
|
/*[clinic end generated code: output=2e571970daf1e5c3 input=7e0adef8e8ccdf2e]*/
|
||||||
|
{
|
||||||
|
assert(self != NULL);
|
||||||
|
assert(PyLong_Check(self));
|
||||||
|
|
||||||
|
PyLongObject *z = (PyLongObject *)self;
|
||||||
|
Py_ssize_t ndigits = Py_ABS(Py_SIZE(z));
|
||||||
|
Py_ssize_t bit_count = 0;
|
||||||
|
|
||||||
|
/* Each digit has up to PyLong_SHIFT ones, so the accumulated bit count
|
||||||
|
from the first PY_SSIZE_T_MAX/PyLong_SHIFT digits can't overflow a
|
||||||
|
Py_ssize_t. */
|
||||||
|
Py_ssize_t ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);
|
||||||
|
for (Py_ssize_t i = 0; i < ndigits_fast; i++) {
|
||||||
|
bit_count += popcount_digit(z->ob_digit[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *result = PyLong_FromSsize_t(bit_count);
|
||||||
|
if (result == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use Python integers if bit_count would overflow. */
|
||||||
|
for (Py_ssize_t i = ndigits_fast; i < ndigits; i++) {
|
||||||
|
PyObject *x = PyLong_FromLong(popcount_digit(z->ob_digit[i]));
|
||||||
|
if (x == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PyObject *y = long_add((PyLongObject *)result, (PyLongObject *)x);
|
||||||
|
Py_DECREF(x);
|
||||||
|
if (y == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_DECREF(result);
|
||||||
|
result = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_DECREF(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
int.as_integer_ratio
|
int.as_integer_ratio
|
||||||
|
@ -5460,6 +5529,7 @@ static PyMethodDef long_methods[] = {
|
||||||
{"conjugate", long_long_meth, METH_NOARGS,
|
{"conjugate", long_long_meth, METH_NOARGS,
|
||||||
"Returns self, the complex conjugate of any int."},
|
"Returns self, the complex conjugate of any int."},
|
||||||
INT_BIT_LENGTH_METHODDEF
|
INT_BIT_LENGTH_METHODDEF
|
||||||
|
INT_BIT_COUNT_METHODDEF
|
||||||
INT_TO_BYTES_METHODDEF
|
INT_TO_BYTES_METHODDEF
|
||||||
INT_FROM_BYTES_METHODDEF
|
INT_FROM_BYTES_METHODDEF
|
||||||
INT_AS_INTEGER_RATIO_METHODDEF
|
INT_AS_INTEGER_RATIO_METHODDEF
|
||||||
|
|
Loading…
Reference in New Issue