Skip to content

Commit 74e3ea6

Browse files
committed
bpo-29882: Add an efficient popcount method for integers
1 parent 5ae299a commit 74e3ea6

File tree

6 files changed

+123
-2
lines changed

6 files changed

+123
-2
lines changed

Doc/library/stdtypes.rst

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

480480
.. versionadded:: 3.1
481481

482+
.. method:: int.bit_count()
483+
484+
Return the number of ones in the binary representation of the integer,
485+
excluding the sign bit::
486+
487+
>>> n = -19
488+
>>> bin(n)
489+
'-0b10011'
490+
>>> n.bit_count()
491+
3
492+
493+
Equivalent to::
494+
495+
def bit_count(self):
496+
return bin(self).count("1")
497+
498+
.. versionadded:: 3.8
499+
482500
.. method:: int.to_bytes(length, byteorder, \*, signed=False)
483501

484502
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
@@ -665,7 +665,7 @@ def non_Python_modules(): r"""
665665
True
666666
>>> real_tests = [t for t in tests if len(t.examples) > 0]
667667
>>> len(real_tests) # objects that actually have doctests
668-
12
668+
13
669669
>>> for t in real_tests:
670670
... print('{} {}'.format(len(t.examples), t.name))
671671
...
@@ -678,6 +678,7 @@ def non_Python_modules(): r"""
678678
1 builtins.hex
679679
1 builtins.int
680680
3 builtins.int.as_integer_ratio
681+
2 builtins.int.bit_count
681682
2 builtins.int.bit_length
682683
5 builtins.memoryview.hex
683684
1 builtins.oct

Lib/test/test_long.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,17 @@ def test_bit_length(self):
10081008
self.assertEqual((a+1).bit_length(), i+1)
10091009
self.assertEqual((-a-1).bit_length(), i+1)
10101010

1011+
def test_bit_count(self):
1012+
for a in range(-1000, 1000):
1013+
self.assertEqual(a.bit_count(), bin(a).count("1"))
1014+
1015+
for exp in [10, 17, 63, 64, 65, 1009, 70234, 1234567]:
1016+
a = 2**exp
1017+
self.assertEqual(a.bit_count(), 1)
1018+
self.assertEqual((a - 1).bit_count(), exp)
1019+
self.assertEqual((a ^ 63).bit_count(), 7)
1020+
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
1021+
10111022
def test_round(self):
10121023
# check round-half-even algorithm. For round to nearest ten;
10131024
# rounding map is invariant under adding multiples of 20
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :meth:`int.bit_count()`, counting the number of ones in the binary
2+
representation of an integer. Patch by Niklas Fiekas.

Objects/clinic/longobject.c.h

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/longobject.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,17 @@ bits_in_digit(digit d)
807807
return d_bits;
808808
}
809809

810+
static int
811+
popcount_digit(digit d)
812+
{
813+
/* 32bit SWAR popcount. */
814+
uint32_t u = d;
815+
u -= (u >> 1) & 0x55555555;
816+
u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
817+
u = (u + (u >> 4)) & 0x0f0f0f0f;
818+
return (u * 0x01010101) >> 24;
819+
}
820+
810821
size_t
811822
_PyLong_NumBits(PyObject *vv)
812823
{
@@ -5358,6 +5369,60 @@ int_bit_length_impl(PyObject *self)
53585369
return NULL;
53595370
}
53605371

5372+
/*[clinic input]
5373+
int.bit_count
5374+
5375+
Number of ones in the binary representation of self.
5376+
5377+
>>> bin(13)
5378+
'0b1101'
5379+
>>> (13).bit_count()
5380+
3
5381+
[clinic start generated code]*/
5382+
5383+
static PyObject *
5384+
int_bit_count_impl(PyObject *self)
5385+
/*[clinic end generated code: output=2e571970daf1e5c3 input=a428900d3e39a606]*/
5386+
{
5387+
Py_ssize_t ndigits, i, bit_count = 0;
5388+
PyLongObject *result, *x, *y;
5389+
5390+
assert(self != NULL);
5391+
assert(PyLong_Check(self));
5392+
5393+
ndigits = Py_ABS(Py_SIZE(self));
5394+
5395+
for (i = 0; i < ndigits && i < PY_SSIZE_T_MAX/PyLong_SHIFT; i++) {
5396+
bit_count += popcount_digit(((PyLongObject *)self)->ob_digit[i]);
5397+
}
5398+
5399+
result = (PyLongObject *)PyLong_FromSsize_t(bit_count);
5400+
if (result == NULL) {
5401+
return NULL;
5402+
}
5403+
5404+
/* Use Python integers if bit_count would overflow. */
5405+
for (; i < ndigits; i++) {
5406+
x = (PyLongObject *)PyLong_FromLong(popcount_digit(((PyLongObject *)self)->ob_digit[i]));
5407+
if (x == NULL) {
5408+
goto error;
5409+
}
5410+
y = (PyLongObject *)long_add(result, x);
5411+
Py_DECREF(x);
5412+
if (y == NULL) {
5413+
goto error;
5414+
}
5415+
Py_DECREF(result);
5416+
result = y;
5417+
}
5418+
5419+
return (PyObject *)result;
5420+
5421+
error:
5422+
Py_DECREF(result);
5423+
return NULL;
5424+
}
5425+
53615426
#if 0
53625427
static PyObject *
53635428
long_is_finite(PyObject *v)
@@ -5522,6 +5587,7 @@ static PyMethodDef long_methods[] = {
55225587
{"conjugate", long_long_meth, METH_NOARGS,
55235588
"Returns self, the complex conjugate of any int."},
55245589
INT_BIT_LENGTH_METHODDEF
5590+
INT_BIT_COUNT_METHODDEF
55255591
#if 0
55265592
{"is_finite", (PyCFunction)long_is_finite, METH_NOARGS,
55275593
"Returns always True."},

0 commit comments

Comments
 (0)