555 lines
14 KiB
TeX
555 lines
14 KiB
TeX
\chapter{Bit manipulation}
|
|
|
|
A computer internally manipulates data
|
|
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 for manipulating them.
|
|
It turns out that there are many uses for
|
|
bit operations in the implementation of algorithms.
|
|
|
|
\section{Bit representation}
|
|
|
|
\index{bit representation}
|
|
|
|
The \key{bit representation} of a number
|
|
indicates which powers of two form the number.
|
|
For example, the bit representation of the number 43
|
|
is 101011 because
|
|
$43 = 2^5 + 2^3 + 2^1 + 2^0$ where
|
|
bits 0, 1, 3 and 5 from the right are ones,
|
|
and all other bits are zeros.
|
|
|
|
The length of a bit representation of a number
|
|
in a computer is static, and depends on the
|
|
data type chosen.
|
|
For example, the \texttt{int} type in C++ is
|
|
usually a 32-bit type, and an \texttt{int} number
|
|
consists of 32 bits.
|
|
In this case, 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}.
|
|
The first bit of a signed number is the sign
|
|
($+$ or $-$), and we can represent numbers
|
|
$-2^{n-1} \ldots 2^{n-1}-1$ using $n$ bits.
|
|
In an unsigned number, in turn,
|
|
all bits belong to the number and we
|
|
can represent numbers $0 \ldots 2^n-1$ using $n$ bits.
|
|
|
|
In an signed bit representation,
|
|
the first bit of a nonnegative number is 0,
|
|
and the first bit of a negative number is 1.
|
|
\key{Two's complement} is used which means that
|
|
the opposite number of a number can be calculated
|
|
by first inversing all the bits in the number,
|
|
and then increasing the number by one.
|
|
|
|
For example, the representation of $-43$
|
|
as an \texttt{int} number is as follows:
|
|
|
|
\[11111111111111111111111111010101\]
|
|
|
|
The connection between signed and unsigned numbers
|
|
is that the representations of a signed
|
|
number $-x$ and an unsigned number $2^n-x$
|
|
are equal.
|
|
Thus, the above representation corresponds to
|
|
the unsigned number $2^{32}-43$.
|
|
|
|
In C++, the numbers are signed as default,
|
|
but we can create unsigned numbers by
|
|
using the keyword \texttt{unsigned}.
|
|
For example, in the code
|
|
\begin{lstlisting}
|
|
int x = -43;
|
|
unsigned int y = x;
|
|
cout << x << "\n"; // -43
|
|
cout << y << "\n"; // 4294967253
|
|
\end{lstlisting}
|
|
the signed number
|
|
$x=-43$ becomes the unsigned number $y=2^{32}-43$.
|
|
|
|
If a number becomes too large or too small for the
|
|
bit representation chosen, it will overflow.
|
|
In practice, 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 code
|
|
\begin{lstlisting}
|
|
int x = 2147483647
|
|
cout << x << "\n"; // 2147483647
|
|
x++;
|
|
cout << x << "\n"; // -2147483648
|
|
\end{lstlisting}
|
|
we increase $2^{31}-1$ by one to get $-2^{31}$.
|
|
|
|
\section{Bit operations}
|
|
|
|
\newcommand\XOR{\mathbin{\char`\^}}
|
|
|
|
\subsubsection{And operation}
|
|
|
|
\index{and operation}
|
|
|
|
The \key{and} operation $x$ \& $y$ produces a number
|
|
that has bit 1 in positions where both the numbers
|
|
$x$ and $y$ have bit 1.
|
|
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.
|
|
|
|
\subsubsection{Or operation}
|
|
|
|
\index{or operation}
|
|
|
|
The \key{or} operation $x$ | $y$ produces a number
|
|
that has bit 1 in positions where at least one
|
|
of the numbers
|
|
$x$ and $y$ have bit 1.
|
|
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 bit 1 in positions where exactly one
|
|
of the numbers
|
|
$x$ and $y$ have bit 1.
|
|
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 inversed.
|
|
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$ produces a number
|
|
where the bits of $x$ have been moved $k$ steps to
|
|
the left by adding $k$ zero bits to the number.
|
|
The right bit shift $x > > k$ produces a number
|
|
where the bits of $x$ have been moved $k$ steps
|
|
to the right by removing $k$ last bits from the number.
|
|
|
|
For example, $14 < < 2 = 56$
|
|
because $14$ equals 1110,
|
|
and it becomes $56$ that equals 111000.
|
|
Correspondingly, $49 > > 3 = 6$
|
|
because $49$ equals 110001,
|
|
and it becomes $6$ that equals 110.
|
|
|
|
Note that the left bit shift $x < < k$
|
|
corresponds to multiplying $x$ by $2^k$,
|
|
and the right bit shift $x > > k$
|
|
corresponds to dividing $x$ by $2^k$
|
|
rounding downwards.
|
|
|
|
\subsubsection{Bit manipulation}
|
|
|
|
The bits in a number are indexed from the right
|
|
to the left beginning from zero.
|
|
A number of the form $1 < < k$ contains a one bit
|
|
in position $k$, and all other bits are zero,
|
|
so we can manipulate single bits of numbers
|
|
using these numbers.
|
|
|
|
The $k$th bit in $x$ is one if
|
|
$x$ \& $(1 < < k) = (1 < < k)$.
|
|
The formula $x$ | $(1 < < k)$
|
|
sets the $k$th bit of $x$ to one,
|
|
the formula
|
|
$x$ \& \textasciitilde $(1 < < k)$
|
|
sets the $k$th bit of $x$ to zero,
|
|
and the formula
|
|
$x$ $\XOR$ $(1 < < k)$
|
|
inverses 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)$, in turn,
|
|
inverses 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 contains the following
|
|
functions for bit manipulation:
|
|
|
|
\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 following code shows how to use the functions:
|
|
\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 support \texttt{int} numbers,
|
|
but there are also \texttt{long long} versions
|
|
of the functions
|
|
available with the prefix \texttt{ll}.
|
|
|
|
\section{Bit representation of 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}
|
|
|