552 lines
14 KiB
TeX
552 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{Dynaaminen ohjelmointi}
|
|
|
|
\subsubsection{Permutaatioista osajoukoiksi}
|
|
|
|
Dynaamisen ohjelmoinnin avulla on usein mahdollista
|
|
muuttaa permutaatioiden läpikäynti osajoukkojen läpikäynniksi.
|
|
Tällöin dynaamisen ohjelmoinnin tilana on
|
|
joukon osajoukko sekä mahdollisesti muuta tietoa.
|
|
|
|
Tekniikan hyötynä on,
|
|
että $n$-alkioisen joukon permutaatioiden määrä $n!$
|
|
on selvästi suurempi kuin osajoukkojen määrä $2^n$.
|
|
Esimerkiksi jos $n=20$, niin $n!=2432902008176640000$,
|
|
kun taas $2^n=1048576$.
|
|
Niinpä tietyillä $n$:n arvoilla permutaatioita ei ehdi
|
|
käydä läpi mutta osajoukot ehtii käydä läpi.
|
|
|
|
Lasketaan esimerkkinä, monessako
|
|
joukon $\{0,1,\ldots,n-1\}$
|
|
permutaatiossa ei ole
|
|
missään kohdassa kahta peräkkäistä lukua.
|
|
Esimerkiksi tapauksessa $n=4$ ratkaisuja on kaksi:
|
|
\begin{itemize}
|
|
\item $(1,3,0,2)$
|
|
\item $(2,0,3,1)$
|
|
\end{itemize}
|
|
|
|
Merkitään $f(x,k)$:llä,
|
|
monellako tavalla osajoukon
|
|
$x$ luvut voi järjestää niin,
|
|
että viimeinen luku on $k$ ja missään kohdassa
|
|
ei ole kahta peräkkäistä lukua.
|
|
Esimerkiksi $f(\{0,1,3\},1)=1$,
|
|
koska voidaan muodostaa permutaatio $(0,3,1)$,
|
|
ja $f(\{0,1,3\},3)=0$, koska 0 ja 1 eivät
|
|
voi olla peräkkäin alussa.
|
|
|
|
Funktion $f$ avulla ratkaisu tehtävään
|
|
on summa
|
|
|
|
\[ \sum_{i=0}^{n-1} f(\{0,1,\ldots,n-1\},i). \]
|
|
|
|
\noindent
|
|
Dynaamisen ohjelmoinnin tilat voi
|
|
tallentaa seuraavasti:
|
|
|
|
\begin{lstlisting}
|
|
long long d[1<<n][n];
|
|
\end{lstlisting}
|
|
|
|
\noindent
|
|
Perustapauksena $f(\{k\},k)=1$ kaikilla $k$:n arvoilla:
|
|
|
|
\begin{lstlisting}
|
|
for (int i = 0; i < n; i++) d[1<<i][i] = 1;
|
|
\end{lstlisting}
|
|
|
|
\noindent
|
|
Tämän jälkeen muut funktion arvot
|
|
saa laskettua seuraavasti:
|
|
|
|
\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
|
|
Muuttujassa $b$ on osajoukon bittiesitys,
|
|
ja osajoukon luvuista muodostettu
|
|
permutaatio on muotoa $(\ldots,j,i)$.
|
|
Vaatimukset ovat, että lukujen $i$ ja $j$
|
|
etäisyyden tulee olla yli 1
|
|
ja lukujen tulee olla osajoukossa $b$.
|
|
|
|
Lopuksi ratkaisujen määrän saa laskettua näin
|
|
muuttujaan $s$:
|
|
|
|
\begin{lstlisting}
|
|
long long s = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
s += d[(1<<n)-1][i];
|
|
}
|
|
\end{lstlisting}
|
|
|
|
\subsubsection{Osajoukkojen summat}
|
|
|
|
Oletetaan sitten, että jokaista
|
|
joukon $\{0,1,\ldots,n-1\}$
|
|
osajoukkoa $x$ vastaa arvo $c(x)$ ja
|
|
tehtävänä on laskea kullekin
|
|
osajoukolle $x$ summa
|
|
\[s(x)=\sum_{y \subset x} c(y) \]
|
|
eli bittimuodossa ilmaistuna
|
|
\[s(x)=\sum_{y \& x = y} c(y). \]
|
|
Seuraavassa on esimerkki funktioiden arvoista,
|
|
kun $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}
|
|
Esimerkiksi $s(110)=c(000)+c(010)+c(100)+c(110)=5$.
|
|
|
|
Tehtävä on mahdollista ratkaista ajassa $O(2^n n)$
|
|
laskemalla arvoja funktiolle $f(x,k)$:
|
|
mikä on lukujen $c(y)$ summa, missä $x$:stä saa $y$:n
|
|
muuttamalla millä tahansa tavalla bittien $0,1,\ldots,k$
|
|
joukossa ykkösbittejä nollabiteiksi.
|
|
Tämän funktion avulla ilmaistuna $s(x)=f(x,n-1)$.
|
|
|
|
Funktion pohjatapaukset ovat:
|
|
\begin{equation*}
|
|
f(x,0) = \begin{cases}
|
|
c(x) & \textrm{jos $x$:n bitti 0 on 0}\\
|
|
c(x)+c(x \XOR 1) & \textrm{jos $x$:n bitti 0 on 1}\\
|
|
\end{cases}
|
|
\end{equation*}
|
|
Suuremmille $k$:n arvoille pätee seuraava rekursio:
|
|
\begin{equation*}
|
|
f(x,k) = \begin{cases}
|
|
f(x,k-1) & \textrm{jos $x$:n bitti $k$ on 0}\\
|
|
f(x,k-1)+f(x \XOR (1 < < k),k-1) & \textrm{jos $x$:n bitti $k$ on 1}\\
|
|
\end{cases}
|
|
\end{equation*}
|
|
|
|
Niinpä funktion arvot voi laskea seuraavasti
|
|
dynaamisella ohjelmoinnilla.
|
|
Koodi olettaa, että taulukko \texttt{c} sisältää
|
|
funktion $c$ arvot ja muodostaa taulukon \texttt{s},
|
|
jossa on funktion $s$ arvot.
|
|
\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}
|
|
|
|
Itse asiassa saman laskennan voi toteuttaa lyhyemmin
|
|
seuraavasti niin, että tulokset lasketaan
|
|
suoraan taulukkoon \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}
|
|
|