We\'ll turn now to two final structures that exploit the fact
The key to understanding what's going on is to recognize that BITSPERWORD
= 2SHIFT
. Thus, x[i>>SHIFT]
finds which 32-bit element of the array x
has the bit corresponding to i
. (By shifting i
5 bits to the right, you're simply dividing by 32.) Once you have located the correct element of x
, the lower 5 bits of i
can then be used to find which particular bit of x[i>>SHIFT]
corresponds to i
. That's what i & MASK
does; by shifting 1 by that number of bits, you move the bit corresponding to 1 to the exact position within x[i>>SHIFT]
that corresponds to the i
th bit in x
.
Here's a bit more of an explanation:
Imagine that we want capacity for N
bits in our bit vector. Since each int
holds 32 bits, we will need (N + 31) / 32
int
values for our storage (that is, N/32 rounded up). Within each int
value, we will adopt the convention that bits are ordered from least significant to most significant. We will also adopt the convention that the first 32 bits of our vector are in x[0]
, the next 32 bits are in x[1]
, and so forth. Here's the memory layout we are using (showing the bit index in our bit vector corresponding to each bit of memory):
+----+----+-------+----+----+----+
x[0]: | 31 | 30 | . . . | 02 | 01 | 00 |
+----+----+-------+----+----+----+
x[1]: | 63 | 62 | . . . | 34 | 33 | 32 |
+----+----+-------+----+----+----+
etc.
Our first step is to allocate the necessary storage capacity:
x = new int[(N + BITSPERWORD - 1) >> SHIFT]
(We could make provision for dynamically expanding this storage, but that would just add complexity to the explanation.)
Now suppose we want to access bit i
(either to set it, clear it, or just to know its current value). We need to first figure out which element of x
to use. Since there are 32 bits per int
value, this is easy:
subscript for x = i / 32
Making use of the enum constants, the x
element we want is:
x[i >> SHIFT]
(Think of this as a 32-bit-wide window into our N-bit vector.) Now we have to find the specific bit corresponding to i
. Looking at the memory layout, it's not hard to figure out that the first (rightmost) bit in the window corresponds to bit index 32 * (i >> SHIFT)
. (The window starts afteri >> SHIFT
slots in x
, and each slot has 32 bits.) Since that's the first bit in the window (position 0), then the bit we're interested in is is at position
i - (32 * (i >> SHIFT))
in the windows. With a little experimenting, you can convince yourself that this expression is always equal to i % 32
(actually, that's one definition of the mod operator) which, in turn, is always equal to i & MASK
. Since this last expression is the fastest way to calculate what we want, that's what we'll use.
From here, the rest is pretty simple. We start with a single bit in the least-significant position of the window (that is, the constant 1
), and move it to the left by i & MASK
bits to get it to the position in the window corresponding to bit i
in the bit vector. This is where the expression
1 << (i & MASK)
comes from. With the bit now moved to where we want it, we can use this as a mask to set, clear, or query the value of the bit at that position in x[i>>SHIFT]
and we know that we're actually setting, clearing, or querying the value of bit i
in our bit vector.