561 lines
15 KiB
TeX
561 lines
15 KiB
TeX
\chapter{Bit manipulation}
|
|
|
|
All data in a program is internally stored as bits,
|
|
i.e., as numbers 0 and 1.
|
|
In this chapter, we will learn how integers
|
|
are represented as bits, and how bit operations
|
|
can be used to manipulate them.
|
|
It turns out that there are many uses for
|
|
bit operations in algorithm programming.
|
|
|
|
\section{Bit representation}
|
|
|
|
\index{bit representation}
|
|
|
|
Every nonnegative integer can be represented as a sum
|
|
\[c_k 2^k + \ldots + c_2 2^2 + c_1 2^1 + c_0 2^0,\]
|
|
where each coefficient $c_i$ is either 0 or 1,
|
|
and the bit representation of such a number is
|
|
$c_k \cdots c_2 c_1 c_0$.
|
|
For example, the number 43 corresponds to the sum
|
|
\[1 \cdot 2^5 + 0 \cdot 2^4 + 1 \cdot 2^3 + 0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0,\]
|
|
so the bit representation of the number is 101011.
|
|
|
|
In programming, the length of the bit representation
|
|
depends on the data type chosen.
|
|
For example, in C++ the type \texttt{int} is
|
|
usually a 32-bit type and an \texttt{int} number
|
|
consists of 32 bits.
|
|
Thus, the bit representation of 43
|
|
as an \texttt{int} number is as follows:
|
|
\[00000000000000000000000000101011\]
|
|
|
|
The bit representation of a number is either
|
|
\key{signed} or \key{unsigned}.
|
|
Usually a signed representation is used,
|
|
which means that both negative and positive
|
|
numbers can be represented.
|
|
A signed number of $n$ bits can contain any
|
|
integer between $2^{n-1}$ and $2^{n-1}-1$.
|
|
For example, the \texttt{int} type in C++ is
|
|
a signed type, and it can contain any
|
|
integer between $2^{31}$ and $2^{31}-1$.
|
|
|
|
The first bit in a signed representation
|
|
is the sign of the number (0 for nonnegative numbers
|
|
and 1 for negative numbers), and
|
|
the remaining $n-1$ bits contain the value of the number.
|
|
\key{Two's complement} is used, which means that the
|
|
opposite number of a number is calculated by first
|
|
inverting all the bits in the number,
|
|
and then increasing the number by one.
|
|
|
|
For example, the bit representation of $-43$
|
|
as an \texttt{int} number is as follows:
|
|
\[11111111111111111111111111010101\]
|
|
|
|
In a signed representation, only nonnegative
|
|
numbers can be used, but the upper bound of the numbers is larger.
|
|
A signed number of $n$ bits can contain any
|
|
integer between $0$ and $2^n-1$.
|
|
For example, the \texttt{unsigned int} type in C++
|
|
can contain any integer between $0$ and $2^{32}-1$.
|
|
|
|
There is a connection between signed and unsigned
|
|
representations:
|
|
a number $-x$ in a signed representation
|
|
equals the number $2^n-x$ in an unsigned representation.
|
|
For example, the following code shows that
|
|
the signed number $x=-43$ equals the unsigned
|
|
number $y=2^{32}-43$:
|
|
\begin{lstlisting}
|
|
int x = -43;
|
|
unsigned int y = x;
|
|
cout << x << "\n"; // -43
|
|
cout << y << "\n"; // 4294967253
|
|
\end{lstlisting}
|
|
|
|
If a number is larger than the upper bound
|
|
of the bit representation, the number will overflow.
|
|
In a signed representation,
|
|
the next number after $2^{n-1}-1$ is $-2^{n-1}$,
|
|
and in an unsigned representation,
|
|
the next number after $2^{n-1}$ is $0$.
|
|
For example, in the following code,
|
|
the next number after $2^{31}-1$ is $-2^{31}$:
|
|
\begin{lstlisting}
|
|
int x = 2147483647
|
|
cout << x << "\n"; // 2147483647
|
|
x++;
|
|
cout << x << "\n"; // -2147483648
|
|
\end{lstlisting}
|
|
|
|
\section{Bit operations}
|
|
|
|
\newcommand\XOR{\mathbin{\char`\^}}
|
|
|
|
\subsubsection{And operation}
|
|
|
|
\index{and operation}
|
|
|
|
The \key{and} operation $x$ \& $y$ produces a number
|
|
that has one bits in positions where both
|
|
$x$ and $y$ have one bits.
|
|
For example, $22$ \& $26$ = 18, because
|
|
|
|
\begin{center}
|
|
\begin{tabular}{rrr}
|
|
& 10110 & (22)\\
|
|
\& & 11010 & (26) \\
|
|
\hline
|
|
= & 10010 & (18) \\
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
Using the and operation, we can check if a number
|
|
$x$ is even because
|
|
$x$ \& $1$ = 0 if $x$ is even, and
|
|
$x$ \& $1$ = 1 if $x$ is odd.
|
|
More generally, $x$ is divisible by $2^k$
|
|
exactly when $x$ \& $(2^k-1)$ = 0.
|
|
|
|
\subsubsection{Or operation}
|
|
|
|
\index{or operation}
|
|
|
|
The \key{or} operation $x$ | $y$ produces a number
|
|
that has one bits in positions where at least one
|
|
of $x$ and $y$ have one bits.
|
|
For example, $22$ | $26$ = 30, because
|
|
|
|
\begin{center}
|
|
\begin{tabular}{rrr}
|
|
& 10110 & (22)\\
|
|
| & 11010 & (26) \\
|
|
\hline
|
|
= & 11110 & (30) \\
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
\subsubsection{Xor operation}
|
|
|
|
\index{xor operation}
|
|
|
|
The \key{xor} operation $x$ $\XOR$ $y$ produces a number
|
|
that has one bits in positions where exactly one
|
|
of $x$ and $y$ have one bits.
|
|
For example, $22$ $\XOR$ $26$ = 12, because
|
|
|
|
\begin{center}
|
|
\begin{tabular}{rrr}
|
|
& 10110 & (22)\\
|
|
$\XOR$ & 11010 & (26) \\
|
|
\hline
|
|
= & 01100 & (12) \\
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
\subsubsection{Not operation}
|
|
|
|
\index{not operation}
|
|
|
|
The \key{not} operation \textasciitilde$x$
|
|
produces a number where all the bits of $x$
|
|
have been inverted.
|
|
The formula \textasciitilde$x = -x-1$ holds,
|
|
for example, \textasciitilde$29 = -30$.
|
|
|
|
The result of the not operation at the bit level
|
|
depends on the length of the bit representation,
|
|
because the operation changes all bits.
|
|
For example, if the numbers are 32-bit
|
|
\texttt{int} numbers, the result is as follows:
|
|
|
|
\begin{center}
|
|
\begin{tabular}{rrrr}
|
|
$x$ & = & 29 & 00000000000000000000000000011101 \\
|
|
\textasciitilde$x$ & = & $-30$ & 11111111111111111111111111100010 \\
|
|
\end{tabular}
|
|
\end{center}
|
|
|
|
\subsubsection{Bit shifts}
|
|
|
|
\index{bit shift}
|
|
|
|
The left bit shift $x < < k$ appends $k$
|
|
zeros to the end of the number,
|
|
and the right bit shift $x > > k$
|
|
removes the $k$ last bits from the number.
|
|
For example, $14 < < 2 = 56$,
|
|
because $14$ equals 1110
|
|
and $56$ equals 111000.
|
|
Similarily, $49 > > 3 = 6$,
|
|
because $49$ equals 110001
|
|
and $6$ equals 110.
|
|
|
|
Note that $x < < k$
|
|
corresponds to multiplying $x$ by $2^k$,
|
|
and $x > > k$
|
|
corresponds to dividing $x$ by $2^k$
|
|
rounded down to an integer.
|
|
|
|
\subsubsection{Applications}
|
|
|
|
A number of the form $1 < < k$ has a one bit
|
|
in position $k$, and all other bits are zero,
|
|
so we can use such numbers to access single bits of numbers.
|
|
For example, the $k$th bit of a number is one
|
|
exactly when $x$ \& $(1 < < k)$ is not zero.
|
|
The following code prints the bit representation
|
|
of an \texttt{int} number $x$:
|
|
|
|
\begin{lstlisting}
|
|
for (int i = 31; i >= 0; i--) {
|
|
if (x&(1<<i)) cout << "1";
|
|
else cout << "0";
|
|
}
|
|
\end{lstlisting}
|
|
|
|
It is also possible to modify single bits
|
|
of numbers using a similar idea.
|
|
For example, the expression $x$ | $(1 < < k)$
|
|
sets the $k$th bit of $x$ to one,
|
|
the expression
|
|
$x$ \& \textasciitilde $(1 < < k)$
|
|
sets the $k$th bit of $x$ to zero,
|
|
and the expression
|
|
$x$ $\XOR$ $(1 < < k)$
|
|
inverts the $k$th bit of $x$.
|
|
|
|
The formula $x$ \& $(x-1)$ sets the last
|
|
one bit of $x$ to zero,
|
|
and the formula $x$ \& $-x$ sets all the
|
|
one bits to zero, except for the last one bit.
|
|
The formula $x$ | $(x-1)$
|
|
inverts all the bits after the last one bit.
|
|
Also note that a positive number $x$ is
|
|
of the form $2^k$ if $x$ \& $(x-1) = 0$.
|
|
|
|
\subsubsection*{Additional functions}
|
|
|
|
The g++ compiler provides the following
|
|
functions for counting bits:
|
|
|
|
\begin{itemize}
|
|
\item
|
|
$\texttt{\_\_builtin\_clz}(x)$:
|
|
the number of zeros at the beginning of the number
|
|
\item
|
|
$\texttt{\_\_builtin\_ctz}(x)$:
|
|
the number of zeros at the end of the number
|
|
\item
|
|
$\texttt{\_\_builtin\_popcount}(x)$:
|
|
the number of ones in the number
|
|
\item
|
|
$\texttt{\_\_builtin\_parity}(x)$:
|
|
the parity (even or odd) of the number of ones
|
|
\end{itemize}
|
|
\begin{samepage}
|
|
|
|
The functions can be used as follows:
|
|
\begin{lstlisting}
|
|
int x = 5328; // 00000000000000000001010011010000
|
|
cout << __builtin_clz(x) << "\n"; // 19
|
|
cout << __builtin_ctz(x) << "\n"; // 4
|
|
cout << __builtin_popcount(x) << "\n"; // 5
|
|
cout << __builtin_parity(x) << "\n"; // 1
|
|
\end{lstlisting}
|
|
\end{samepage}
|
|
|
|
The functions can be used with \texttt{int} numbers,
|
|
but there are also \texttt{long long} versions
|
|
of the functions
|
|
available with the prefix \texttt{ll}.
|
|
|
|
\section{Representing sets}
|
|
|
|
Each subset of a set $\{0,1,2,\ldots,n-1\}$
|
|
corresponds to a $n$ bit number
|
|
where the one bits indicate which elements
|
|
are included in the subset.
|
|
For example, the bit representation for $\{1,3,4,8\}$
|
|
is 100011010 that equals $2^8+2^4+2^3+2^1=282$.
|
|
|
|
The bit representation of a set uses little memory
|
|
because only one bit is needed for the information
|
|
whether an element belongs to the set.
|
|
In addition, we can efficiently manipulate sets
|
|
that are stored as bits.
|
|
|
|
\subsubsection{Set operations}
|
|
|
|
In the following code, the variable $x$
|
|
contains a subset of $\{0,1,2,\ldots,31\}$.
|
|
The code adds elements 1, 3, 4 and 8
|
|
to the set and then prints the elements in the set.
|
|
|
|
\begin{lstlisting}
|
|
// x is an empty set
|
|
int x = 0;
|
|
// add numbers 1, 3, 4 and 8 to the set
|
|
x |= (1<<1);
|
|
x |= (1<<3);
|
|
x |= (1<<4);
|
|
x |= (1<<8);
|
|
// print the elements in the set
|
|
for (int i = 0; i < 32; i++) {
|
|
if (x&(1<<i)) cout << i << " ";
|
|
}
|
|
cout << "\n";
|
|
\end{lstlisting}
|
|
|
|
The output of the code is as follows:
|
|
\begin{lstlisting}
|
|
1 3 4 8
|
|
\end{lstlisting}
|
|
|
|
Using the bit representation of a set,
|
|
we can efficiently implement set operations
|
|
using bit operations:
|
|
\begin{itemize}
|
|
\item $a$ \& $b$ is the intersection $a \cap b$ of $a$ and $b$
|
|
(this contains the elements that are in both the sets)
|
|
\item $a$ | $b$ is the union $a \cup b$ of $a$ and $b$
|
|
(this contains the elements that are at least
|
|
in one of the sets)
|
|
\item $a$ \& (\textasciitilde$b$) is the difference
|
|
$a \setminus b$ of $a$ and $b$
|
|
(this contains the elements that are in $a$
|
|
but not in $b$)
|
|
\end{itemize}
|
|
|
|
The following code constructs the union
|
|
of $\{1,3,4,8\}$ and $\{3,6,8,9\}$:
|
|
|
|
\begin{lstlisting}
|
|
// set {1,3,4,8}
|
|
int x = (1<<1)+(1<<3)+(1<<4)+(1<<8);
|
|
// set {3,6,8,9}
|
|
int y = (1<<3)+(1<<6)+(1<<8)+(1<<9);
|
|
// union of the sets
|
|
int z = x|y;
|
|
// print the elements in the union
|
|
for (int i = 0; i < 32; i++) {
|
|
if (z&(1<<i)) cout << i << " ";
|
|
}
|
|
cout << "\n";
|
|
\end{lstlisting}
|
|
|
|
The output of the code is as follows:
|
|
\begin{lstlisting}
|
|
1 3 4 6 8 9
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Iterating through subsets}
|
|
|
|
The following code iterates through
|
|
the subsets of $\{0,1,\ldots,n-1\}$:
|
|
|
|
\begin{lstlisting}
|
|
for (int b = 0; b < (1<<n); b++) {
|
|
// process subset b
|
|
}
|
|
\end{lstlisting}
|
|
The following code goes through
|
|
subsets with exactly $k$ elements:
|
|
\begin{lstlisting}
|
|
for (int b = 0; b < (1<<n); b++) {
|
|
if (__builtin_popcount(b) == k) {
|
|
// process subset b
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
The following code goes through the subsets
|
|
of a set $x$:
|
|
\begin{lstlisting}
|
|
int b = 0;
|
|
do {
|
|
// process subset b
|
|
} while (b=(b-x)&x);
|
|
\end{lstlisting}
|
|
% Esimerkiksi jos $x$ esittää joukkoa $\{2,5,7\}$,
|
|
% niin koodi käy läpi osajoukot
|
|
% $\emptyset$, $\{2\}$, $\{5\}$, $\{7\}$,
|
|
% $\{2,5\}$, $\{2,7\}$, $\{5,7\}$ ja $\{2,5,7\}$.
|
|
|
|
\section{Dynamic programming}
|
|
|
|
\subsubsection{From permutations to subsets}
|
|
|
|
Using dynamic programming, it is often possible
|
|
to change iteration over permutations into
|
|
iteration over subsets.
|
|
In this case, the dynamic programming state
|
|
contains a subset of a set and possibly
|
|
some additional information.
|
|
|
|
The benefit in this technique is that
|
|
$n!$, the number of permutations of an $n$ element set,
|
|
is much larger than $2^n$, the number of subsets.
|
|
For example, if $n=20$, then
|
|
$n!=2432902008176640000$ and $2^n=1048576$.
|
|
Thus, for certain values of $n$,
|
|
we can go through subsets but not through permutations.
|
|
|
|
As an example, let's calculate the number of
|
|
permutations of set $\{0,1,\ldots,n-1\}$
|
|
where the difference between any two successive
|
|
elements is larger than one.
|
|
For example, there are two solutions for $n=4$:
|
|
\begin{itemize}
|
|
\item $(1,3,0,2)$
|
|
\item $(2,0,3,1)$
|
|
\end{itemize}
|
|
|
|
Let $f(x,k)$ denote the number of permutations
|
|
for a subset $x$
|
|
where the last number is $k$ and
|
|
the difference between any two successive
|
|
elements is larger than one.
|
|
For example, $f(\{0,1,3\},1)=1$
|
|
because there is a permutation $(0,3,1)$,
|
|
and $f(\{0,1,3\},3)=0$ because 0 and 1
|
|
can't be next to each other.
|
|
|
|
Using $f$, the solution for the problem is the sum
|
|
|
|
\[ \sum_{i=0}^{n-1} f(\{0,1,\ldots,n-1\},i). \]
|
|
|
|
\noindent
|
|
The dynamic programming states can be stored as follows:
|
|
|
|
\begin{lstlisting}
|
|
long long d[1<<n][n];
|
|
\end{lstlisting}
|
|
|
|
\noindent
|
|
First, $f(\{k\},k)=1$ for all values of $k$:
|
|
|
|
\begin{lstlisting}
|
|
for (int i = 0; i < n; i++) d[1<<i][i] = 1;
|
|
\end{lstlisting}
|
|
|
|
\noindent
|
|
After this, the other values can be calculated
|
|
as follows:
|
|
|
|
\begin{lstlisting}
|
|
for (int b = 0; b < (1<<n); b++) {
|
|
for (int i = 0; i < n; i++) {
|
|
for (int j = 0; j < n; j++) {
|
|
if (abs(i-j) > 1 && (b&(1<<i)) && (b&(1<<j))) {
|
|
d[b][i] += d[b^(1<<i)][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\noindent
|
|
The variable $b$ contains the bit representation
|
|
of the subset, and the corresponding
|
|
permutation is of the form $(\ldots,j,i)$.
|
|
It is required that the difference between
|
|
$i$ and $j$ is larger than 1, and the
|
|
numbers belong to subset $b$.
|
|
|
|
Finally, the number of solutions can be
|
|
calculated as follows to $s$:
|
|
|
|
\begin{lstlisting}
|
|
long long s = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
s += d[(1<<n)-1][i];
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Sums of subsets}
|
|
|
|
Let's assume that every subset $x$
|
|
of $\{0,1,\ldots,n-1\}$
|
|
is assigned a value $c(x)$,
|
|
and our task is to calculate for
|
|
each subset $x$ the sum
|
|
\[s(x)=\sum_{y \subset x} c(y)\]
|
|
that corresponds to the sum
|
|
\[s(x)=\sum_{y \& x = y} c(y)\]
|
|
using bit operations.
|
|
The following table gives an example of
|
|
the values of the functions when $n=3$:
|
|
\begin{center}
|
|
\begin{tabular}{rrr}
|
|
$x$ & $c(x)$ & $s(x)$ \\
|
|
\hline
|
|
000 & 2 & 2 \\
|
|
001 & 0 & 2 \\
|
|
010 & 1 & 3 \\
|
|
011 & 3 & 6 \\
|
|
100 & 0 & 2 \\
|
|
101 & 4 & 6 \\
|
|
110 & 2 & 5 \\
|
|
111 & 0 & 12 \\
|
|
\end{tabular}
|
|
\end{center}
|
|
For example, $s(110)=c(000)+c(010)+c(100)+c(110)=5$.
|
|
|
|
The problem can be solved in $O(2^n n)$ time
|
|
by defining a function $f(x,k)$ that calculates
|
|
the sum of values $c(y)$ where $x$ can be
|
|
converted into $y$ by changing any one bits
|
|
in positions $0,1,\ldots,k$ to zero bits.
|
|
Using this function, the solution for the
|
|
problem is $s(x)=f(x,n-1)$.
|
|
|
|
The base cases for the function are:
|
|
\begin{equation*}
|
|
f(x,0) = \begin{cases}
|
|
c(x) & \textrm{if bit 0 in $x$ is 0}\\
|
|
c(x)+c(x \XOR 1) & \textrm{if bit 0 in $x$ is 1}\\
|
|
\end{cases}
|
|
\end{equation*}
|
|
For larger values of $k$, the following recursion holds:
|
|
\begin{equation*}
|
|
f(x,k) = \begin{cases}
|
|
f(x,k-1) & \textrm{if bit $k$ in $x$ is 0}\\
|
|
f(x,k-1)+f(x \XOR (1 < < k),k-1) & \textrm{if bit $k$ in $x$ is 1}\\
|
|
\end{cases}
|
|
\end{equation*}
|
|
|
|
Thus, we can calculate the values for the function
|
|
as follows using dynamic programming.
|
|
The code assumes that the array \texttt{c}
|
|
contains the values for $c$,
|
|
and it constructs an array \texttt{s}
|
|
that contains the values for $s$.
|
|
\begin{lstlisting}
|
|
for (int x = 0; x < (1<<n); x++) {
|
|
f[x][0] = c[x];
|
|
if (x&1) f[x][0] += c[x^1];
|
|
}
|
|
for (int k = 1; k < n; k++) {
|
|
for (int x = 0; x < (1<<n); x++) {
|
|
f[x][k] = f[x][k-1];
|
|
if (b&(1<<k)) f[x][k] += f[x^(1<<k)][k-1];
|
|
}
|
|
if (k == n-1) s[x] = f[x][k];
|
|
}
|
|
\end{lstlisting}
|
|
|
|
Actually, a much shorter implementation is possible
|
|
because we can calculate the results directly
|
|
to array \texttt{s}:
|
|
\begin{lstlisting}
|
|
for (int x = 0; x < (1<<n); x++) s[x] = c[x];
|
|
for (int k = 0; k < n; k++) {
|
|
for (int x = 0; x < (1<<n); x++) {
|
|
if (x&(1<<k)) s[x] += s[x^(1<<k)];
|
|
}
|
|
}
|
|
\end{lstlisting}
|
|
|