Skip to content

Commit 8bd216d

Browse files
niklasfmdickinson
andauthored
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>
1 parent 364b5ea commit 8bd216d

File tree

7 files changed

+135
-2
lines changed

7 files changed

+135
-2
lines changed

Diff for: Doc/library/stdtypes.rst

+21
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,27 @@ class`. In addition, it provides a few more methods:
478478

479479
.. versionadded:: 3.1
480480

481+
.. method:: int.bit_count()
482+
483+
Return the number of ones in the binary representation of the absolute
484+
value of the integer. This is also known as the population count.
485+
Example::
486+
487+
>>> n = 19
488+
>>> bin(n)
489+
'0b10011'
490+
>>> n.bit_count()
491+
3
492+
>>> (-n).bit_count()
493+
3
494+
495+
Equivalent to::
496+
497+
def bit_count(self):
498+
return bin(self).count("1")
499+
500+
.. versionadded:: 3.10
501+
481502
.. method:: int.to_bytes(length, byteorder, \*, signed=False)
482503

483504
Return an array of bytes representing an integer.

Diff for: Doc/whatsnew/3.10.rst

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Summary -- Release highlights
7070
New Features
7171
============
7272

73+
* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
74+
number of ones in the binary expansion of a given integer, also known
75+
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)
7376

7477

7578
Other Language Changes

Diff for: Lib/test/test_doctest.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ def non_Python_modules(): r"""
669669
True
670670
>>> real_tests = [t for t in tests if len(t.examples) > 0]
671671
>>> len(real_tests) # objects that actually have doctests
672-
13
672+
14
673673
>>> for t in real_tests:
674674
... print('{} {}'.format(len(t.examples), t.name))
675675
...
@@ -682,6 +682,7 @@ def non_Python_modules(): r"""
682682
1 builtins.hex
683683
1 builtins.int
684684
3 builtins.int.as_integer_ratio
685+
2 builtins.int.bit_count
685686
2 builtins.int.bit_length
686687
5 builtins.memoryview.hex
687688
1 builtins.oct

Diff for: Lib/test/test_long.py

+11
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,17 @@ def test_bit_length(self):
10161016
self.assertEqual((a+1).bit_length(), i+1)
10171017
self.assertEqual((-a-1).bit_length(), i+1)
10181018

1019+
def test_bit_count(self):
1020+
for a in range(-1000, 1000):
1021+
self.assertEqual(a.bit_count(), bin(a).count("1"))
1022+
1023+
for exp in [10, 17, 63, 64, 65, 1009, 70234, 1234567]:
1024+
a = 2**exp
1025+
self.assertEqual(a.bit_count(), 1)
1026+
self.assertEqual((a - 1).bit_count(), exp)
1027+
self.assertEqual((a ^ 63).bit_count(), 7)
1028+
self.assertEqual(((a - 1) ^ 510).bit_count(), exp - 8)
1029+
10191030
def test_round(self):
10201031
# check round-half-even algorithm. For round to nearest ten;
10211032
# rounding map is invariant under adding multiples of 20
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.

Diff for: Objects/clinic/longobject.c.h

+26-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Objects/longobject.c

+70
Original file line numberDiff line numberDiff line change
@@ -5304,6 +5304,75 @@ int_bit_length_impl(PyObject *self)
53045304
return NULL;
53055305
}
53065306

5307+
static int
5308+
popcount_digit(digit d)
5309+
{
5310+
/* 32bit SWAR popcount. */
5311+
uint32_t u = d;
5312+
u -= (u >> 1) & 0x55555555U;
5313+
u = (u & 0x33333333U) + ((u >> 2) & 0x33333333U);
5314+
u = (u + (u >> 4)) & 0x0f0f0f0fU;
5315+
return (uint32_t)(u * 0x01010101U) >> 24;
5316+
}
5317+
5318+
/*[clinic input]
5319+
int.bit_count
5320+
5321+
Number of ones in the binary representation of the absolute value of self.
5322+
5323+
Also known as the population count.
5324+
5325+
>>> bin(13)
5326+
'0b1101'
5327+
>>> (13).bit_count()
5328+
3
5329+
[clinic start generated code]*/
5330+
5331+
static PyObject *
5332+
int_bit_count_impl(PyObject *self)
5333+
/*[clinic end generated code: output=2e571970daf1e5c3 input=7e0adef8e8ccdf2e]*/
5334+
{
5335+
assert(self != NULL);
5336+
assert(PyLong_Check(self));
5337+
5338+
PyLongObject *z = (PyLongObject *)self;
5339+
Py_ssize_t ndigits = Py_ABS(Py_SIZE(z));
5340+
Py_ssize_t bit_count = 0;
5341+
5342+
/* Each digit has up to PyLong_SHIFT ones, so the accumulated bit count
5343+
from the first PY_SSIZE_T_MAX/PyLong_SHIFT digits can't overflow a
5344+
Py_ssize_t. */
5345+
Py_ssize_t ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);
5346+
for (Py_ssize_t i = 0; i < ndigits_fast; i++) {
5347+
bit_count += popcount_digit(z->ob_digit[i]);
5348+
}
5349+
5350+
PyObject *result = PyLong_FromSsize_t(bit_count);
5351+
if (result == NULL) {
5352+
return NULL;
5353+
}
5354+
5355+
/* Use Python integers if bit_count would overflow. */
5356+
for (Py_ssize_t i = ndigits_fast; i < ndigits; i++) {
5357+
PyObject *x = PyLong_FromLong(popcount_digit(z->ob_digit[i]));
5358+
if (x == NULL) {
5359+
goto error;
5360+
}
5361+
PyObject *y = long_add((PyLongObject *)result, (PyLongObject *)x);
5362+
Py_DECREF(x);
5363+
if (y == NULL) {
5364+
goto error;
5365+
}
5366+
Py_DECREF(result);
5367+
result = y;
5368+
}
5369+
5370+
return result;
5371+
5372+
error:
5373+
Py_DECREF(result);
5374+
return NULL;
5375+
}
53075376

53085377
/*[clinic input]
53095378
int.as_integer_ratio
@@ -5460,6 +5529,7 @@ static PyMethodDef long_methods[] = {
54605529
{"conjugate", long_long_meth, METH_NOARGS,
54615530
"Returns self, the complex conjugate of any int."},
54625531
INT_BIT_LENGTH_METHODDEF
5532+
INT_BIT_COUNT_METHODDEF
54635533
INT_TO_BYTES_METHODDEF
54645534
INT_FROM_BYTES_METHODDEF
54655535
INT_AS_INTEGER_RATIO_METHODDEF

0 commit comments

Comments
 (0)