\chapter{Bit manipulation} Tietokone käsittelee tietoa sisäisesti bitteinä eli numeroina 0 ja 1. Tässä luvussa tutustumme tarkemmin kokonaisluvun bittiesitykseen sekä bittioperaatioihin, jotka muokkaavat luvun bittejä. Osoittautuu, että näistä operaatioista on monenlaista hyötyä algoritmien ohjelmoinnissa. \section{Luvun bittiesitys} \index{bittiesitys@bittiesitys} Luvun \key{bittiesitys} ilmaisee, mistä 2:n potensseista luku muodostuu. Esimerkiksi luvun 43 bittiesitys on 101011, koska $43 = 2^5 + 2^3 + 2^1 + 2^0$ eli oikealta lukien bitit 0, 1, 3 ja 5 ovat ykkösiä ja kaikki muut bitit ovat nollia. Tietokoneessa luvun bittiesityksen bittien määrä on kiinteä ja riippuu käytetystä tietotyypistä. Esimerkiksi C++:n \texttt{int}-tyyppi on tavallisesti 32-bittinen, jolloin \texttt{int}-luku tallennetaan 32 bittinä. Tällöin esimerkiksi luvun 43 bittiesitys \texttt{int}-lukuna on seuraava: \[00000000000000000000000000101011\] Luvun bittiesitys on joko \key{etumerkillinen} tai \key{etumerkitön}. Etumerkillisen bittiesityksen ensimmäinen bitti on etumerkki ($+$ tai $-$) ja $n$ bitillä voi esittää luvut $-2^{n-1} \ldots 2^{n-1}-1$. Jos taas bittiesitys on etumerkitön, kaikki bitit kuuluvat lukuun ja $n$ bitillä voi esittää luvut $0 \ldots 2^n-1$. Etumerkillisessä bittiesityksessä ei-negatiivisen luvun ensimmäinen bitti on 0 ja negatiivisen luvun ensimmäinen bitti on 1. Bittiesityksenä on \key{kahden komplementti}, jossa luvun luvun vastaluvun saa laskettua muuttamalla kaikki bitit käänteiseksi ja lisäämällä tulokseen yksi. Esimerkiksi luvun $-43$ esitys \texttt{int}-lukuna on seuraava: \[11111111111111111111111111010101\] Etumerkillisen ja etumerkittömän bittiesityksen yhteys on, että etumerkillisen luvun $-x$ ja etumerkittömän luvun $2^n-x$ bittiesitykset ovat samat. Niinpä yllä oleva bittiesitys tarkoittaa etumerkittömänä lukua $2^{32}-43$. C++:ssa luvut ovat oletuksena etumerkillisiä, mutta avainsanan \texttt{unsigned} avulla luvusta saa etumerkittömän. Esimerkiksi koodissa \begin{lstlisting} int x = -43; unsigned int y = x; cout << x << "\n"; // -43 cout << y << "\n"; // 4294967253 \end{lstlisting} etumerkillistä lukua $x=-43$ vastaa etumerkitön luku $y=2^{32}-43$. Jos luvun suuruus menee käytössä olevan bittiesityksen ulkopuolelle, niin luku pyörähtää ympäri. Etumerkillisessä bittiesityksessä luvusta $2^{n-1}-1$ seuraava luku on $-2^{n-1}$ ja vastaavasti etumerkittömässä bittiesityksessä luvusta $2^n-1$ seuraava luku on $0$. Esimerkiksi koodissa \begin{lstlisting} int x = 2147483647 cout << x << "\n"; // 2147483647 x++; cout << x << "\n"; // -2147483648 \end{lstlisting} muuttuja $x$ pyörähtää ympäri luvusta $2^{31}-1$ lukuun $-2^{31}$. \section{Bittioperaatiot} \newcommand\XOR{\mathbin{\char`\^}} \subsubsection{And-operaatio} \index{and-operaatio} And-operaatio $x$ \& $y$ tuottaa luvun, jossa on ykkösbitti niissä kohdissa, joissa molemmissa luvuissa $x$ ja $y$ on ykkösbitti. Esimerkiksi $22$ \& $26$ = 18, koska \begin{center} \begin{tabular}{rrr} & 10110 & (22)\\ \& & 11010 & (26) \\ \hline = & 10010 & (18) \\ \end{tabular} \end{center} And-operaation avulla voi tarkastaa luvun parillisuuden, koska $x$ \& $1$ = 0, jos luku on parillinen, ja $x$ \& $1$ = 1, jos luku on pariton. \subsubsection{Or-operaatio} \index{or-operaatio} Or-operaatio $x$ | $y$ tuottaa luvun, jossa on ykkösbitti niissä kohdissa, joissa ainakin toisessa luvuista $x$ ja $y$ on ykkösbitti. Esimerkiksi $22$ | $26$ = 30, koska \begin{center} \begin{tabular}{rrr} & 10110 & (22)\\ | & 11010 & (26) \\ \hline = & 11110 & (30) \\ \end{tabular} \end{center} \subsubsection{Xor-operaatio} \index{xor-operaatio} Xor-operaatio $x$ $\XOR$ $y$ tuottaa luvun, jossa on ykkösbitti niissä kohdissa, joissa tarkalleen toisessa luvuista $x$ ja $y$ on ykkösbitti. Esimerkiksi $22$ $\XOR$ $26$ = 12, koska \begin{center} \begin{tabular}{rrr} & 10110 & (22)\\ $\XOR$ & 11010 & (26) \\ \hline = & 01100 & (12) \\ \end{tabular} \end{center} \subsubsection{Not-operaatio} \index{not-operaatio} Not-operaatio \textasciitilde$x$ tuottaa luvun, jossa kaikki $x$:n bitit on muutettu käänteisiksi. Operaatiolle pätee kaava \textasciitilde$x = -x-1$, esimerkiksi \textasciitilde$29 = -30$. Not-operaation toiminta bittitasolla riippuu siitä, montako bittiä luvun bittiesityksessä on, koska operaatio vaikuttaa kaikkiin luvun bitteihin. Esimerkiksi 32-bittisenä \texttt{int}-lukuna tilanne on seuraava: \begin{center} \begin{tabular}{rrrr} $x$ & = & 29 & 00000000000000000000000000011101 \\ \textasciitilde$x$ & = & $-30$ & 11111111111111111111111111100010 \\ \end{tabular} \end{center} \subsubsection{Bittisiirrot} \index{bittisiirto@bittisiirto} Vasen bittisiirto $x < < k$ tuottaa luvun, jossa luvun $x$ bittejä on siirretty $k$ askelta vasemmalle eli luvun loppuun tulee $k$ nollabittiä. Oikea bittisiirto $x > > k$ tuottaa puolestaan luvun, jossa luvun $x$ bittejä on siirretty $k$ askelta oikealle eli luvun lopusta lähtee pois $k$ viimeistä bittiä. Esimerkiksi $14 < < 2 = 56$, koska $14$ on bitteinä 1110, josta tulee bittisiirron jälkeen 111000 eli $56$. Vastaavasti $49 > > 3 = 6$, koska $49$ on bitteinä 110001, josta tulee bittisiirron jälkeen 110 eli $6$. Huomaa, että vasen bittisiirto $x < < k$ vastaa luvun $x$ kertomista $2^k$:lla ja oikea bittisiirto $x > > k$ vastaa luvun $x$ jakamista $2^k$:lla alaspäin pyöristäen. \subsubsection{Bittien käsittely} Luvun bitit indeksoidaan oikealta vasemmalle nollasta alkaen. Luvussa $1 < < k$ on yksi ykkösbitti kohdassa $k$ ja kaikki muut bitit ovat nollia, joten sen avulla voi käsitellä muiden lukujen yksittäisiä bittejä. Luvun $x$ bitti $k$ on ykkösbitti, jos $x$ \& $(1 < < k) = (1 < < k)$. Lauseke $x$ | $(1 < < k)$ asettaa luvun $x$ bitin $k$ ykköseksi, lauseke $x$ \& \textasciitilde $(1 < < k)$ asettaa luvun $x$ bitin $k$ nollaksi ja lauseke $x$ $\XOR$ $(1 < < k)$ muuttaa luvun $x$ bitin $k$ käänteiseksi. % % Seuraava koodi muuttaa luvun bittejä: % % \begin{lstlisting} % int x = 181; // 10110101 % cout << (x|(1<<2)) << "\n"; // 181 = 10110101 % cout << (x|(1<<3)) << "\n"; // 189 = 10111101 % cout << (x&~(1<<2)) << "\n"; // 177 = 10110001 % cout << (x&~(1<<3)) << "\n"; // 181 = 10110101 % cout << (x^(1<<2)) << "\n"; // 177 = 10110001 % cout << (x^(1<<3)) << "\n"; // 189 = 10111101 % \end{lstlisting} % % % Bittiesityksen vasemmanpuoleisin bitti on eniten merkitsevä % % (\textit{most significant}) ja % % oikeanpuoleisin bitti on vähiten merkitsevä (\textit{least significant}). Lauseke $x$ \& $(x-1)$ muuttaa luvun $x$ viimeisen ykkösbitin nollaksi, ja lauseke $x$ \& $-x$ nollaa luvun $x$ kaikki bitit paitsi viimeisen ykkösbitin. Lauseke $x$ | $(x-1)$ vuorostaan muuttaa kaikki viimeisen ykkösbitin jälkeiset bitit ykkösiksi. Huomaa myös, että positiivinen luku $x$ on muotoa $2^k$, jos $x$ \& $(x-1) = 0$. % % Seuraava koodi esittelee operaatioita: % % \begin{lstlisting} % int x = 168; // 10101000 % cout << (x&(x-1)) << "\n"; // 160 = 10100000 % cout << (x&-x) << "\n"; // 8 = 00001000 % cout << (x|(x-1)) << "\n"; // 175 = 10101111 % \end{lstlisting} \subsubsection*{Lisäfunktiot} Kääntäjä g++ sisältää mm. seuraavat funktiot bittien käsittelyyn: \begin{itemize} \item $\texttt{\_\_builtin\_clz}(x)$: nollien määrä bittiesityksen alussa \item $\texttt{\_\_builtin\_ctz}(x)$: nollien määrä bittiesityksen lopussa \item $\texttt{\_\_builtin\_popcount}(x)$: ykkösten määrä bittiesityksessä \item $\texttt{\_\_builtin\_parity}(x)$: ykkösten määrän parillisuus \end{itemize} \begin{samepage} Nämä funktiot käsittelevät \texttt{int}-lukuja, mutta funktioista on myös \texttt{long long} -versiot, joiden lopussa on pääte \texttt{ll}. Seuraava koodi esittelee funktioiden käyttöä: \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} \section{Joukon bittiesitys} Joukon $\{0,1,2,\ldots,n-1\}$ jokaista osajoukkoa vastaa $n$-bittinen luku, jossa ykkösbitit ilmaisevat, mitkä alkiot ovat mukana osajoukossa. Esimerkiksi joukkoa $\{1,3,4,8\}$ vastaa bittiesitys 100011010 eli luku $2^8+2^4+2^3+2^1=282$. Joukon bittiesitys vie vähän muistia, koska tieto kunkin alkion kuulumisesta osajoukkoon vie vain yhden bitin tilaa. Lisäksi bittimuodossa tallennettua joukkoa on tehokasta käsitellä bittioperaatioilla. \subsubsection{Joukon käsittely} Seuraavan koodin muuttuja $x$ sisältää joukon $\{0,1,2,\ldots,31\}$ osajoukon. Koodi lisää luvut 1, 3, 4 ja 8 joukkoon ja tulostaa joukon sisällön. \begin{lstlisting} // x on tyhjä joukko int x = 0; // lisätään luvut 1, 3, 4 ja 8 joukkoon x |= (1<<1); x |= (1<<3); x |= (1<<4); x |= (1<<8); // tulostetaan joukon sisältö for (int i = 0; i < 32; i++) { if (x&(1< 1 && (b&(1<