Bitové operácie

Úvod do dvojkovej sústavy ste mali v predchádzajúcej lekcii Čísla v dvojkovej sústave. Teraz si ukážeme, ako vieme s binárnymi číslami pracovať v Pythone, a na čo je to dobré.

Binárne čísla ešte raz

V Pythone pracujeme s číslami v desiatkovej sústave, ako to robíme kdekoľvek inde. Avšak Python si vie čísla reprezentovať v rôznych sústavách, ku ktorým sa vieme dostať napríklad takto:

# Desiatkova (decimalna) sustava
x = 42

# Dvojkova (binarna) sustava
a = bin(x)

# Osmickova (oktalna) sustava
b = oct(x)

# Sestnastkova (hexadecimalna) sustava
c = hex(x)

print(f'dec: {x}, bin: {a}, oct: {b}, hex: {c}')
# dec: 42, bin: 0b101010, oct: 0o52, hex: 0x2a

V tejto lekcii sa zameriame ďalej len na binárne čísla.

Ako môžete vidieť, binárne číslo je len rad núl a jednotiek (hovoríme tomu aj bitový reťazec, bit string, pretože tie nuly a jednotky voláme bity), ktorý špecifikuje dané číslo. V Pythone vieme zadať číslo aj rovno v binárnom tvare, Python si ho potom sám prekonvertuje do desiatkovej sústavy. Urobíme to tak, že k bitovému reťazcu pridáme prefix 0b, ktorý Pythonu hovorí, že nasledujúci reťazec reprezentuje binárne číslo:

x = 0b101010

print(x)
# 42

print(x == 42)
# True

Bitové operácie

Bitové operácie pracujú na bitovom reťazci, ktorý prechádzajú po jednom bite, na ktorom vykonávajú nejakú operáciu.

Bitové operácie sú pre programátorov veľmi dôležité, pretože každý výpočet, ktorý prebehne v počítači je jedna z týchto operácií. Počítač v najzákladnejšej forme nevie robiť nič iné, ako spracovávať binárne čísla pomocou binárnych operácií, avšak len tento základ stačí na to, aby sme vedeli robiť aritmetiku v desiatkovej sústave, spúšťať algoritmy a nakoniec robiť všetko, čo dokáže moderný počítač.

Operácia AND

Poďme sa pozrieť na našu prvú bitovú operáciu, ktorú možno z logiky poznáte ako konjunkcia alebo v bežnej reči "a" alebo AND. Operácia AND prechádza dva binárne reťazce po bitoch, na miestach, kde sa zhodujú na jednotke vráti jednotku, inak nulu. V Pythone operátor pre AND značíme ako &:

a = 0b101010    # 42
b = 0b110011    # 51

print(bin(a & b))
# 0b10010

V tomto bode vám možno napadne, že takéto prechádzanie dvoch binárnych reťazcov naraz bit po bite funguje len na bitové reťazce rovnakej dĺžky. Čo ak chceme robiť AND napríklad na číslach 42 (101010) a 455 (111000111)? Rieši sa to jednoducho, iba kratšiemu reťazcu pripíšeme na začiatku nuly, podobne ako keby sme to robili v desiatkovej sústave. Python to, samozrejme, robí automaticky.

Operácia OR

Okrem AND máme aj operáciu OR, teda "alebo", respektíve z logiky to možno poznáte ako disjunkciu. Podobne ako pri AND, znova prechádzame dva binárne reťazce naraz bit po bite, ibaže tentokrát vo výstupnom reťazci bude jednotka na všetkých miestach, kde aspoň jeden reťazec má jednotku. Operátor OR v Pythone značíme ako |:

a = 0b101010    # 42
b = 0b110100    # 52

print(bin(a | b))
# 0b111110

Operácia NOT

Ďalšou bitovou operáciou je negácia alebo NOT, ktorá pracuje len s jedným bitovým reťazcom, pričom z každého nulového bitu spraví jednotkový a naopak. Teda ak máme napríklad reťazec 1010 (v desiatkovej sústave 10), tak po negácii bude z neho 0101 (v desiatkovej sústave 5). V Pythone operátor negácie značíme ako ~.

Tu to začína byť zaujímavejšie. Ako sme spomínali pred chvíľou, tak, ako napríklad číslo 42 v desiatkovej sústave vieme v zásade zapísať aj ako 042, 0042, atď., tak aj binárne číslo 101010 vieme zapísať ako 0101010, 00101010, alebo aj 000000000000101010. Keď urobíme NOT na čísle 101010, dostaneme číslo 010101. Z tohoto čísla ale Python oreže začiatočné nuly (tak ako ich oreže z čísla 042), takže dostaneme číslo 10101.

Ako teda získať z negácie presne toľko bitov, ako by sme chceli? S týmto problémom sa vieme popasovať tak, že ohraničíme, s akým dlhým binárnym reťazcom pracujeme. To vieme urobiť napríklad tak, že na výsledok negácie aplikujeme ešte AND s binárnym reťazcom tvoreným jednotkami. Napríklad, ak chceme, aby sme mali výsledok s dĺžkou 8 bitov, AND aplikujeme na znegované číslo a binárne číslo, ktoré má 8 jednotiek:

a = 0b101010      # 42
b = 0b11111111    # 255
c = ~a & b

print(bin(c))
# 0b11010101

Ak chceme pootáčať len bity, ktoré máme v pôvodnom binárnom reťazci a, logicky sa to dá spraviť napríklad jedným z týchto spôsobov:

print(bin(~a & 0b111111))
# 0b10101

print(bin(~a & 2 ** 6 - 1))
# 0b10101

Zamyslite sa, ako funguje ten druhý spôsob, a prečo sme ako výsledok dostali 5 bitov, keď sme ich na začiatku mali 6.

Operácia XOR

Trochu vyššie sme sa bavili o operácii OR. Operácia XOR je niečo podobné.

XOR sa celým menom volá exclusive OR alebo teda exkluzívna disjunkcia. Exkluzívna preto, že vráti jednotku, iba ak sú dva bity rozdielne. XOR sa teda správa rovnako ako OR, avšak s tým rozdielom, že neakceptuje prípad, keď sa zhodujú jednotky. Normálny OR preto voláme tiež aj inkluzívna disjunkcia.

V slovenčine XOR zodpovedá spojeniu "buď, alebo", čiže naproti normálnemu "alebo" nepripúšťame, že by sa dve veci mohli stať zároveň.

V Pythone operátor XOR značíme ako ^:

a = 0b101010    # 42
b = 0b110100    # 52

print(bin(a ^ b))
# 0b11110

Vo výsledku vyššie si opäť môžete všimnúť, že Python nám z neho odstránil prvú nulu.

XOR sa v praxi často využíva, keď chceme zistiť, na ktorých miestach sa dva binárne reťazce líšia, alebo spočítať počet takýchto miest.