Byte-table population count 2026-10-25
Store the Hamming weight of each possible byte in a 256-entry table. Extract each byte of a word by a logical right shift and a bit mask, then sum its table entries. The byte positions partition the word's bits, proving the formula. A 32-bit word needs four lookups, which can be unrolled. Logical extraction does not depend on the endianness of a stored word. The speed advantage depends on table-access costs as well as instruction counts.
Logical right shift 2026-10-24
A logical right shift moves bits toward less significant positions, discards those shifted out, and fills the high positions with zero. On the unsigned interpretation of a machine word, it computes the quotient in integer division by . Indeed, splitting the binary representation after its lowest bits gives with ; the retained upper bits represent . A fixed-width left bit shift by followed by a logical right shift by extracts precisely the low bits, giving the remainder.
Use a logical right shift to discard one inspected bit at a time. The following Thumb instruction set subroutine accepts the word in
r0, returns its population count in r0, preserves r4, and otherwise uses caller-saved processor registers.popcount_loop:
push {r4}
movs r1, #0
movs r2, #1
movs r3, #32
pop_loop:
movs r4, r0
ands r4, r4, r2
adds r1, r1, r4
lsrs r0, r0, #1
subs r3, r3, #1
bne pop_loop
movs r0, r1
pop {r4}
bx lrands extracts the current least significant bit; adds includes it in the count. lsrs fills the newly vacant high position with zero. subs immediately precedes bne, so the branch tests the zero flag from decrementing the loop counter, not a flag left by the shift.After iterations, the accumulator contains the number of one bits in the original word's lowest positions, and the remaining word is the original unsigned interpretation shifted right by . This loop invariant holds initially and is preserved by each iteration. At every position has been inspected, proving the result. The given example therefore returns 10, and the all-zero and all-one words return 0 and 32. The accumulator cannot overflow: it never exceeds 32.
A logical right shift by moves into position and fills the high positions with zero. Its unsigned interpretation isMeanwhile the original unsigned interpretation decomposes asBy integer division, , exactly the unsigned quotient. Thus the source instruction puts the required quotient into
r1. This is a proof from binary representation, rather than an assumption that every kind of right shift implements every kind of division. The displayed bit argument applies to , with endpoint operations implemented as a copy or zero where appropriate.For a 32-bit machine word and , use a left bit shift followed by a logical right shift:The left shift discards all but the lowest bits and moves those retained bits to the top. The logical right shift returns them to their original positions and fills the rest with zero. Hence
lsls r1, r0, #(32-k)
lsrs r1, r1, #(32-k)r1 contains , the remainder identified in (b). For the remainder is zero; for it is , so these endpoints can be handled directly.On a reduced instruction set computer, a bit mask such as may not fit an instruction's immediate field. Masking may therefore require materializing the constant in another processor register, possibly using several instructions or a memory load. Small shift counts are directly encodable, so the two shifts can save code space, a temporary processor register, or a load. This is an architecture-dependent advantage, rather than a universal speed claim. The source's compact instruction forms support these immediate shifts, while their bitwise AND form takes its other operand from a processor register.[ref]
Thumb 16-bit instruction set quick reference by Arm. page 1, Logical and Shift/rotate tables
Let , let , and let denote the least nonnegative residue. Here is a signed 32-bit two's complement value. The source instructions, annotated with their derived values, are:The first instruction is an arithmetic right shift, so it replicates the sign bit. The second is a logical right shift, turning the all-one word into . The paired shifts then extract the low ten bits, as in (c). Their effect is the residue of even if its two's complement interpretation is negative, because divides the word modulus . The adjusted value is representable for every input: positive inputs are unchanged, and negative inputs gain only .
rem1024:
asrs r3, r0, #31 // r3 = -s: all zero bits or all one bits
lsrs r3, r3, #22 // r3 = b = 1023*s
adds r0, r0, r3 // r0 = a+b
lsls r0, r0, #22 // r0 has word value (a+b)*2^22 modulo 2^32
lsrs r0, r0, #22 // r0 = u = (a+b) modulo 1024, in 0,...,1023
subs r0, r0, r3 // r0 = u-b
bx lr // return u-bFor , , so the return value is the usual nonnegative remainder . For , put as a mathematical integer, and write with . If , then and the return value is zero. If , thenThese are precisely the remainders for C integer division, whose quotient truncates towards zero. In every case the result has the dividend's sign or is zero, has magnitude below , and satisfies . The proof includes the smallest representable input: was used only in mathematical reasoning, not as an overflowing machine operation.[ref]
C11 committee draft N1570. section 6.5.5 paragraph 6, quotient truncation and the quotient–remainder identity
Page 1 lists the compact logical and shift instruction forms. Left bit shifts have immediate counts –, while logical right shifts and arithmetic right shifts have immediate counts –. The logical bitwise AND entry uses a processor register operand. These encoding details explain why extracting low bits with two immediate shifts can avoid materializing a bit mask.