Skip to content

Commit 1c6e9fa

Browse files
committed
bpo-29882: Add an efficient popcount method for integers
1 parent bd3d8ba commit 1c6e9fa

File tree

5 files changed

+116
-2
lines changed

5 files changed

+116
-2
lines changed

Doc/library/stdtypes.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,24 @@ class`. In addition, it provides a few more methods:
474474

475475
.. versionadded:: 3.1
476476

477+
.. method:: int.bit_count()
478+
479+
Return the number of ones in the binary representation of the integer,
480+
exluding the sign bit::
481+
482+
>>> n = -19
483+
>>> bin(n)
484+
'-0b10011'
485+
>>> n.bit_count()
486+
3
487+
488+
Equivalent to::
489+
490+
def bit_count(self):
491+
return bin(self).count("1")
492+
493+
.. versionadded:: 3.7
494+
477495
.. method:: int.to_bytes(length, byteorder, \*, signed=False)
478496

479497
Return an array of bytes representing an integer.

Lib/test/test_doctest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ def non_Python_modules(): r"""
663663
True
664664
>>> real_tests = [t for t in tests if len(t.examples) > 0]
665665
>>> len(real_tests) # objects that actually have doctests
666-
8
666+
9
667667
>>> for t in real_tests:
668668
... print('{} {}'.format(len(t.examples), t.name))
669669
...
@@ -673,6 +673,7 @@ def non_Python_modules(): r"""
673673
2 builtins.float.hex
674674
1 builtins.hex
675675
1 builtins.int
676+
2 builtins.int.bit_count
676677
2 builtins.int.bit_length
677678
1 builtins.oct
678679

Lib/test/test_long.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,17 @@ def test_bit_length(self):
973973
self.assertEqual((a+1).bit_length(), i+1)
974974
self.assertEqual((-a-1).bit_length(), i+1)
975975

976+
def test_bit_count(self):
977+
for a in range(-1000, 1000):
978+
self.assertEqual(a.bit_count(), bin(a).count("1"))
979+
980+
for exp in [10, 17, 63, 64, 65, 1009, 70234, 1234567]:
981+
a = 2**exp
982+
self.assertEqual(a.bit_count(), 1)
983+
self.assertEqual((a - 1).bit_count(), exp)
984+
self.assertEqual((a ^ 63).bit_count(), 7)
985+
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
986+
976987
def test_round(self):
977988
# check round-half-even algorithm. For round to nearest ten;
978989
# rounding map is invariant under adding multiples of 20

Objects/clinic/longobject.c.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,29 @@ int_bit_length(PyObject *self, PyObject *Py_UNUSED(ignored))
118118
return int_bit_length_impl(self);
119119
}
120120

121+
PyDoc_STRVAR(int_bit_count__doc__,
122+
"bit_count($self, /)\n"
123+
"--\n"
124+
"\n"
125+
"Number of ones in the binary representation of self.\n"
126+
"\n"
127+
">>> bin(13)\n"
128+
"\'0b1101\'\n"
129+
">>> (13).bit_count()\n"
130+
"3");
131+
132+
#define INT_BIT_COUNT_METHODDEF \
133+
{"bit_count", (PyCFunction)int_bit_count, METH_NOARGS, int_bit_count__doc__},
134+
135+
static PyObject *
136+
int_bit_count_impl(PyObject *self);
137+
138+
static PyObject *
139+
int_bit_count(PyObject *self, PyObject *Py_UNUSED(ignored))
140+
{
141+
return int_bit_count_impl(self);
142+
}
143+
121144
PyDoc_STRVAR(int_to_bytes__doc__,
122145
"to_bytes($self, /, length, byteorder, *, signed=False)\n"
123146
"--\n"
@@ -211,4 +234,4 @@ int_from_bytes(PyTypeObject *type, PyObject **args, Py_ssize_t nargs, PyObject *
211234
exit:
212235
return return_value;
213236
}
214-
/*[clinic end generated code: output=c1ce9c11929b0bab input=a9049054013a1b77]*/
237+
/*[clinic end generated code: output=ace06d130801077b input=a9049054013a1b77]*/

Objects/longobject.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,16 @@ bits_in_digit(digit d)
737737
return d_bits;
738738
}
739739

740+
static int
741+
popcount_digit(digit d)
742+
{
743+
/* 32bit SWAR popcount. */
744+
d -= (d >> 1) & 0x55555555;
745+
d = (d & 0x33333333) + ((d >> 2) & 0x33333333);
746+
d = (d + (d >> 4)) & 0x0f0f0f0f;
747+
return (d * 0x01010101) >> 24;
748+
}
749+
740750
size_t
741751
_PyLong_NumBits(PyObject *vv)
742752
{
@@ -5168,6 +5178,56 @@ int_bit_length_impl(PyObject *self)
51685178
return NULL;
51695179
}
51705180

5181+
/*[clinic input]
5182+
int.bit_count
5183+
5184+
Number of ones in the binary representation of self.
5185+
5186+
>>> bin(13)
5187+
'0b1101'
5188+
>>> (13).bit_count()
5189+
3
5190+
[clinic start generated code]*/
5191+
5192+
static PyObject *
5193+
int_bit_count_impl(PyObject *self)
5194+
/*[clinic end generated code: output=2e571970daf1e5c3 input=a428900d3e39a606]*/
5195+
{
5196+
Py_ssize_t ndigits, i, bit_count = 0;
5197+
PyLongObject *result, *x, *y;
5198+
5199+
assert(self != NULL);
5200+
assert(PyLong_Check(self));
5201+
5202+
ndigits = Py_ABS(Py_SIZE(self));
5203+
5204+
for (i = 0; i < ndigits && i < PY_SSIZE_T_MAX/PyLong_SHIFT; i++)
5205+
bit_count += popcount_digit(((PyLongObject *)self)->ob_digit[i]);
5206+
5207+
result = (PyLongObject *)PyLong_FromSsize_t(bit_count);
5208+
if (result == NULL)
5209+
return NULL;
5210+
5211+
/* Use Python integers if bit_count would overflow. */
5212+
for (; i < ndigits; i++) {
5213+
x = (PyLongObject *)PyLong_FromLong(popcount_digit(((PyLongObject *)self)->ob_digit[i]));
5214+
if (x == NULL)
5215+
goto error;
5216+
y = (PyLongObject *)long_add(result, x);
5217+
Py_DECREF(x);
5218+
if (y == NULL)
5219+
goto error;
5220+
Py_DECREF(result);
5221+
result = y;
5222+
}
5223+
5224+
return (PyObject *)result;
5225+
5226+
error:
5227+
Py_DECREF(result);
5228+
return NULL;
5229+
}
5230+
51715231
#if 0
51725232
static PyObject *
51735233
long_is_finite(PyObject *v)
@@ -5296,6 +5356,7 @@ static PyMethodDef long_methods[] = {
52965356
{"conjugate", (PyCFunction)long_long, METH_NOARGS,
52975357
"Returns self, the complex conjugate of any int."},
52985358
INT_BIT_LENGTH_METHODDEF
5359+
INT_BIT_COUNT_METHODDEF
52995360
#if 0
53005361
{"is_finite", (PyCFunction)long_is_finite, METH_NOARGS,
53015362
"Returns always True."},

0 commit comments

Comments
 (0)