Operatory Bitowe (&, |, ^, <<, >>) na Maturze z Informatyki
Operatory bitowe to specjalne operatory, które nie działają na liczbie jako całości (np. 123), ale bezpośrednio na jej *bitach* (czyli jej reprezentacji binarnej, np. 1111011). Zamiast wykonywać dodawanie czy mnożenie, wykonują operacje logiczne (AND, OR, XOR) na każdej parze bitów jednocześnie. Na przykład, porównując 5 (101) i 3 (011), porównują pierwszy bit z pierwszym, drugi z drugim itd. Są to operacje 'niskopoziomowe', co oznacza, że są *ekstremalnie* szybkie – często wykonywane w jednym cyklu procesora. Na maturze pozwalają na sprytne i wydajne rozwiązywanie niektórych zadań, zwłaszcza tych 'na papierze'.
Dlaczego to ważne? Musisz znać operatory bitowe, aby rozwiązać specyficzne zadania maturalne, które wprost o nie pytają (jak zadanie z XOR w 2023 roku). Są też fundamentem do zrozumienia, jak komputer przechowuje dane, jak działają maski bitowe (np. do sprawdzania, czy dany bit jest 'zapalony') oraz do wykonywania superszybkich operacji mnożenia i dzielenia przez potęgi dwójki (za pomocą przesunięć bitowych).
Teoria
Główna idea polega na traktowaniu liczb jako ciągów bitów. Operacja bitowa jest stosowana 'kolumnowo' do każdej pary bitów. Np. dla `5 & 3`: ` 101` (5) `& 011` (3) `----` ` 001` (1) Dzieje się tak, bo: `1&0=0`, `0&1=0`, `1&1=1`.
Jak to działa?
- `&` (Bitowe AND): Zwraca 1 *tylko* jeśli oba bity na tej samej pozycji to 1.
- `|` (Bitowe OR): Zwraca 1 jeśli *przynajmniej jeden* z bitów na tej pozycji to 1.
- `^` (Bitowe XOR - 'albo'): Zwraca 1 jeśli bity na tej pozycji są *różne*.
- `<<` (Przesunięcie w lewo): Przesuwa wszystkie bity w lewo, dopisując zera z prawej. `x << n` to szybkie mnożenie $x * 2^n$.
- `>>` (Przesunięcie w prawo): Przesuwa wszystkie bity w prawo, 'wyrzucając' te z prawej. `x >> n` to szybkie dzielenie całkowite $x // 2^n$.
- `~` (Bitowe NOT): Odwraca wszystkie bity (0 na 1, 1 na 0). Rzadziej używane na maturze ze względu na komplikacje z liczbami ujemnymi (kod U2).
Złożoność: Wszystkie operatory bitowe są operacjami o czasie stałym, O(1) (lub O(k) gdzie k to liczba bitów w liczbie, np. 32/64, co jest stałą). Są to jedne z najszybszych operacji, jakie może wykonać procesor.
Implementacja
Operatory AND, OR, XOR
Pythona = 5 # binarnie: 101
b = 3 # binarnie: 011 (uzupełniamy zerem z przodu)
print(f"a = {a:03b}, b = {b:03b}") # :03b - binarnie, z zerami do 3 cyfr
# AND (&): 101 & 011 = 001
print(f"a & b = {a & b} ({a & b:03b})") # Wynik: 1 (001)
# OR (|): 101 | 011 = 111
print(f"a | b = {a | b} ({a | b:03b})") # Wynik: 7 (111)
# XOR (^): 101 ^ 011 = 110
print(f"a ^ b = {a ^ b} ({a ^ b:03b})") # Wynik: 6 (110)To podstawowe użycie. Python (i C++) wykonuje operacje na bitach. `101 & 011` daje `001`, ponieważ tylko ostatnia kolumna miała `1 & 1`. `101 | 011` daje `111`, bo w każdej kolumnie występował przynajmniej jeden bit `1`. Wyniki są pokazane zarówno dziesiętnie, jak i binarnie.
Przesunięcia bitowe (Shift) - Mnożenie i Dzielenie
Pythonn = 10 # binarnie: 1010
# Przesunięcie w lewo o 1 (mnożenie przez 2^1 = 2)
# 1010 -> 10100 (z 10 robi się 20)
print(f"{n} << 1 = {n << 1}") # Wynik: 20
# Przesunięcie w lewo o 3 (mnożenie przez 2^3 = 8)
# 1010 -> 1010000 (z 10 robi się 80)
print(f"{n} << 3 = {n << 3}") # Wynik: 80
# Przesunięcie w prawo o 1 (dzielenie całkowite przez 2^1 = 2)
# 1010 -> 101 (z 10 robi się 5)
print(f"{n} >> 1 = {n >> 1}") # Wynik: 5
# Przesunięcie w prawo o 2 (dzielenie całkowite przez 2^2 = 4)
# 1010 -> 10 (z 10 robi się 2)
print(f"{n} >> 2 = {n >> 2}") # Wynik: 2Przesunięcia bitowe to superszybki sposób na mnożenie i dzielenie przez potęgi dwójki. `n << k` to to samo co `n * (2**k)`. `n >> k` to to samo co `n // (2**k)`.
Wzorzec: Maska bitowa (sprawdzanie bitu)
Python# Jak sprawdzić, czy 3. bit (licząc od 0) w liczbie n jest '1'?
# 3. bit odpowiada wartości 2^3 = 8 (binarnie: 1000)
# To jest nasza 'maska'
n = 13 # binarnie: 1101
maska = 1 << 3 # To jest 8, czyli binarnie 1000
# Używamy AND. Jeśli wynik jest > 0, to bit był zapalony.
# 1101 (13)
# & 1000 (maska 8)
# ------
# 1000 (8, co jest > 0)
if (n & maska) > 0:
print("3. bit jest ustawiony (1)")
else:
print("3. bit jest 0")
# Sprawdzenie bitu 0 (wartość 2^0 = 1)
if (n & 1) == 0:
print("Liczba jest parzysta")
else:
print("Liczba jest nieparzysta")To najważniejsze zastosowanie operatora `&`. Tworzymy 'maskę' z jedną jedynką na pozycji, którą chcemy sprawdzić (używając przesunięcia `1 << k`). Operacja `n & maska` 'wygasza' wszystkie bity oprócz tego, który nas interesuje. Jeśli wynik jest niezerowy, to bit był ustawiony.
Przykładowe Zadania Maturalne
Napisz program, który wczyta z pliku 'liczby.txt' 200 liczb całkowitych (każda w nowej linii). Podaj, ile z tych liczb ma *dokładnie jeden* ustawiony bit '1' w swojej reprezentacji binarnej (są to potęgi liczby 2, np. 1, 2, 4, 8, 16...).
Wskazówka: Liczba jest potęgą dwójki (ma jeden bit '1'), jeśli spełnia sprytny warunek bitowy: `n > 0 AND (n & (n - 1)) == 0`. Na przykład dla n=8: `n = 1000`, `n-1 = 0111`. `1000 & 0111 = 0000`. Dla n=6: `n = 110`, `n-1 = 101`. `110 & 101 = 100` (nie jest zerem).
Pokaż szkic rozwiązania
1. Otwórz plik 'liczby.txt'.
2. Zainicjuj `licznik_poteg_dwojki = 0`.
3. Dla każdej linii w pliku:
a. Wczytaj `n = int(linia.strip())`.
b. Sprawdź warunek: `if n > 0 and (n & (n - 1)) == 0:`
c. ` licznik_poteg_dwojki += 1`
4. Wypisz `licznik_poteg_dwojki`.
5. (Alternatywnie: `if bin(n).count('1') == 1:`)Oblicz (123_10 XOR 101101_2) XOR 2D_16. Wynik podaj w systemie dziesiętnym.
Wskazówka: To zadanie 'na papierze', które wprost testuje znajomość operatora XOR. Aby je wykonać, musisz sprowadzić wszystkie trzy liczby do jednego, wspólnego systemu (np. dziesiętnego lub binarnego) i wykonać operacje po kolei.
Pokaż szkic rozwiązania
1. Zamień wszystkie liczby na system dziesiętny: a. `123_10` = 123. b. `101101_2` = $1*32 + 0*16 + 1*8 + 1*4 + 0*2 + 1*1 = 32 + 8 + 4 + 1 = 45$. c. `2D_16` = $2*16 + 13*1 = 32 + 13 = 45$ (ponieważ D=13). 2. Wykonaj operacje XOR (w Pythonie/C++ to operator `^`): a. Oblicz pierwszy nawias: `123 ^ 45`. b. `123` to `1111011_2`. c. `45` to `0101101_2`. d. `1111011 ^ 0101101 = 1010110_2`. e. `1010110_2` to $64 + 16 + 4 + 2 = 86$. f. Oblicz drugą operację: `86 ^ 45`. g. `86` to `1010110_2`. h. `45` to `0101101_2`. i. `1010110 ^ 0101101 = 1111011_2`. 3. Zamień wynik z powrotem na dziesiętny (jeśli liczysz binarnie): a. `1111011_2` to $64 + 32 + 16 + 8 + 2 + 1 = 123$. 4. Odpowiedź: 123.
Częste Błędy
❌ Mylenie operatorów bitowych (`&`, `|`) z logicznymi (`and`, `or`)
To krytyczny błąd! `if x > 10 and y > 10` (logiczne) sprawdza dwa warunki. `if x & y` (bitowe) wykonuje operację na bitach. `4 and 2` da `True` (bo 2 jest 'prawdziwe'), ale `4 & 2` (`100 & 010`) da `0` (`False`).
Poprawka: Do łączenia warunków `if` używaj `and`, `or`. Do manipulacji bitami używaj `&`, `|`, `^`.
❌ Python: Używanie `^` do potęgowania
W wielu językach (i matematyce) `^` oznacza potęgę. W Pythonie i C++ `^` to **XOR**. Do potęgowania w Pythonie służy operator `**`.
Poprawka: Zapamiętaj: `2 ^ 3` to `1` (bo `10 ^ 11 = 01`), a `2 ** 3` to `8`.
❌ Dziwne wyniki negacji (`~`)
Początkujący wpisuje `~5` (`~101`) i spodziewa się `010` (2). Zamiast tego dostaje `-6`. Dzieje się tak z powodu używania przez komputery kodu uzupełnień do dwóch (U2) do reprezentacji liczb ujemnych.
Poprawka: Unikaj operatora `~` na maturze, jeśli nie jesteś absolutnie pewien, jak działa kod U2. Do wyzerowania bitu lepiej użyć maski z `&` (np. `n & 0b111011`).
❌ Zła kolejność działań (precedens)
Operatory bitowe mają niższą kolejność niż arytmetyczne i czasem niższą niż porównania. Kod `if n & 1 == 0:` może być zinterpretowany jako `if n & (1 == 0):` czyli `if n & 0:`, co jest zawsze fałszem.
Poprawka: Zawsze, *zawsze* używaj nawiasów przy operacjach bitowych: `if (n & 1) == 0:`.
Kluczowe Wnioski
- Operatory bitowe `&`, `|`, `^` działają na pojedynczych bitach liczby.
- `&` (AND) - 'zapala' bit, gdy oba są zapalone. Świetne do *sprawdzania* bitów (maskowanie).
- `|` (OR) - 'zapala' bit, gdy choć jeden jest zapalony. Świetne do *ustawiania* bitów.
- `^` (XOR) - 'zapala' bit, gdy są różne. Świetne do *przełączania* bitów.
- `<< k` to szybkie mnożenie przez $2^k$.
- `>> k` to szybkie dzielenie całkowite przez $2^k$.
- Zawsze używaj nawiasów dla pewności: `(n & maska) == 0`.
- W Pythonie `^` to XOR, a `**` to potęga.
Najczęściej Zadawane Pytania
❓ Jaka jest różnica między `&` a `and`?
`&` to operator *bitowy*: `5 & 3` (`101 & 011`) daje `1`. `and` to operator *logiczny*: `5 and 3` daje `True` (w Pythonie zwraca `3`, ale w kontekście `if` liczy się jako `True`). `&` działa na bitach, `and` na wartościach logicznych.
❓ Po co mi XOR? Jakie ma właściwości?
XOR ma dwie magiczne właściwości: 1) `x ^ x = 0` (XOR liczby z samą sobą daje zero). 2) `x ^ 0 = x` (XOR z zerem nie zmienia liczby). Jest też odwracalny: `(x ^ y) ^ y = x`. Używa się go w kryptografii, sumach kontrolnych i do 'przełączania' bitów.
❓ Jak sprawdzić, czy liczba jest parzysta za pomocą bitów?
Liczba jest parzysta, jeśli jej ostatni bit (bit $2^0$) jest równy 0. Można to sprawdzić warunkiem `if (n & 1) == 0:`. Jeśli wynik to 0, jest parzysta. Jeśli 1, jest nieparzysta. To jest (marginalnie) szybsze niż `n % 2 == 0`.
❓ Jak ustawić `k`-ty bit liczby `n` na 1?
Użyj operatora OR i maski. `n = n | (1 << k)`. To 'zapali' k-ty bit, nie ruszając pozostałych.
❓ Jak wyzerować `k`-ty bit liczby `n`?
Użyj operatora AND i zanegowanej maski. `n = n & ~(1 << k)`. Maska `~(1 << k)` to liczba, która ma same jedynki, *oprócz* zera na `k`-tej pozycji. Operacja AND zachowa wszystkie bity `n` i wyzeruje ten jeden.
Chcesz opanować wszystkie tematy maturalne?
Dołącz do kursu i zyskaj dostęp do interaktywnych lekcji, edytora kodu i setek zadań.