← Máy tính hoạt động thế nào?

Bài 4 · Vận dụng · 20 phút· Cập nhật 11/06/2026

Phép toán trên bit (bitwise)

Biên soạn bởi Nguyễn Anh Tuấn

AND/OR/XOR/NOT & dịch bit trên cả byte; mẹo chẵn/lẻ bằng & 1; cờ & mặt nạ; vì sao một phép bitwise = một lệnh ALU qua cổng logic.

Ở bài Hệ nhị phân ta thấy mỗi số là một dãy bit (0/1). Ở bài Cổng logic ta thấy AND/OR/XOR/NOT làm việc trên từng bit. Phép toán trên bit (bitwise) ghép hai điều đó: tác động lên từng bit của cả con số cùng lúc - không phải tính theo giá trị thập phân.

  • Phép số học (+, ×, %) làm việc trên GIÁ TRỊ; bitwise làm việc trên từng BIT.
  • Sáu phép thường gặp: AND (&), OR (|), XOR (^), NOT (~), dịch trái («), dịch phải (»).
  • Trong code thật, dịch bit viết là << và >> (C, Python, JavaScript…) - bài này dùng « » cho dễ nhìn.

Mỗi phép áp đúng bảng chân trị của cổng logic, nhưng cho cả 8 cột bit một lượt. Thử bật-tắt từng bit và đổi phép:

1286432168421 DEC
A 178
B 108
AND
0
0
1
0
0
0
0
0
32

Bấm ô bit của A/ B để bật–tắt. Kết quả AND = 32 (hex 0x20). Bit cuối của A là 0 → A là số chẵn (chính là A & 1).

AND giữ bit chung; OR gộp; XOR = khác nhau; NOT lật

  1011 0010   A = 178
& 0110 1100   B = 108
---------
  0010 0000   = 32     AND: ra 1 chi khi CA HAI bit la 1

  1011 0010
| 0110 1100
---------
  1111 1110   = 254    OR: ra 1 neu CO IT NHAT MOT bit 1

  1011 0010
^ 0110 1100
---------
  1101 1110   = 222    XOR: ra 1 khi HAI bit KHAC nhau

~ 1011 0010 = 0100 1101 = 77   NOT: lat tung bit (trong 8-bit)
  • AND (&): lọc/giữ bit - “mặt nạ”. OR (|): bật bit. XOR (^): đảo bit có chọn lọc.
  • NOT (~) lật mọi bit; kết quả phụ thuộc bề rộng (8-bit → ~178 = 77, không phải -179).
  • Mỗi phép xử lý 8 bit độc lập, song song.

Dịch trái « đẩy mọi bit sang trái (thêm 0 ở cuối) = nhân với 2ⁿ. Dịch phải » đẩy sang phải = chia cho 2ⁿ (làm tròn xuống). Hệt như thêm/bớt số 0 ở hệ thập phân là nhân/chia 10.

000000113dịch trái 1 ô → ×2000001106các bit trượt sang trái một ô; dịch phải 1 ô thì ÷2
Dịch trái một ô: mọi bit trượt sang trái, giá trị nhân đôi (3 → 6).

dịch bit

0000 0101  = 5
5 « 3 →
0010 1000  = 40    (= 5 x 8 = 5 x 2^3)   dich trai 3 bit

1100 1000  = 200
200 » 2 →
0011 0010  = 50    (= 200 / 4 = 200 / 2^2)   dich phai 2 bit

Dịch quá tay thì tràn

Bit đẩy ra ngoài 8 bit bị bỏ. 1100 0000 « 1 → bit cao biến mất, còn 1000 0000. Giống giới hạn 8-bit của thanh ghi ở mô hình CPU - đúng ý đồ, không phải lỗi.

Bit cuối (phải nhất) mang giá trị 2⁰ = 1 - phần lẻ duy nhất của số; mọi bit khác đều là bội của 2 (luôn chẵn). Nên n & 1 (giữ đúng bit cuối) cho biết ngay: 1 → lẻ, 0 → chẵn.

bit cuối quyết định chẵn/lẻ

1011 0010 & 1 = 0   ->  178 chan
1011 0011 & 1 = 1   ->  179 le
# n & 1 cho ket qua y het n % 2

Trung thực: “% 2 chậm hơn & 1” là chuyện hoang đường

Nghe thì hợp lý, nhưng sai trên hệ thống hiện đại. Trình biên dịch tự đổi n % 2n & 1, x × 2ⁿx « n, x ÷ 2ⁿ (không dấu) → x » n - ra cùng một lệnh máy. Vậy n % 2 == 0(n & 1) == 0 chạy nhanh như nhau. Đừng viết bitwise chỉ để “qua mặt” phép chia - hãy viết cái dễ đọc. Giá trị thật của bitwise nằm ở chỗ khác (Bước 5-6).

Một số nguyên là một dãy công tắc on/off. Thay vì 8 biến boolean, gói tất cả vào một byte rồi dùng bitwise để bật/tắt/kiểm từng cái - gọn và nhanh. Đây mới là nơi bitwise phát huy hết tác dụng.

quyền truy cập kiểu chmod

READ  = 0b001   # 1
WRITE = 0b010   # 2
EXEC  = 0b100   # 4

quyen = READ | WRITE        # 0b011  -> bat READ va WRITE
co_write = quyen & WRITE     # khac 0  -> CO quyen ghi
quyen = quyen & ~WRITE       # 0b001  -> TAT WRITE (mat na dao)
quyen = quyen ^ EXEC         # 0b101  -> DAO (toggle) EXEC
  • Bật cờ: giá_trị | mask. Tắt cờ: giá_trị & ~mask.
  • Kiểm cờ: (giá_trị & mask) khác 0. Đảo cờ: giá_trị ^ mask.
  • Một số = nhiều boolean: quyền tệp (rwx), cờ tính năng, kênh màu R/G/B, mặt nạ mạng…

Mỗi phép bitwise là MỘT lệnh ALU - và ALU chính là dãy cổng AND/OR/XOR/NOT bạn đã ráp ở bài Cổng logic. Làm A & B trên 8 (hay 32, 64) bit chỉ là một lượt tín hiệu chạy qua dãy cổng đó - không vòng lặp, không phép chia nhiều bước. Đó là nghĩa thật của “chạm thẳng vào transistor”: bạn ra lệnh đúng thứ phần cứng làm tự nhiên nhất.

  • Bitwise = một lệnh ALU = một lượt qua cổng logic (cực nhanh, cố định).
  • “Nhanh” theo nghĩa: làm đúng việc phần cứng sinh ra để làm - không phải mẹo qua mặt % 2.
  • Hiểu bit giúp đọc cờ, mặt nạ, giao thức, định dạng tệp… ở mức gần phần cứng.

Tiếp theo

Bạn đã biết biểu diễn bit (nhị phân), thao tác bit (bitwise) và chúng chạy trên cổng logic ra sao. Bài sau: Bộ nhớ & RAM - những bit ấy được CẤT ở đâu để CPU đọc/ghi.

Câu hỏi thường gặp

Trên hệ thống hiện đại: KHÔNG đáng kể. Trình biên dịch tự đổi n % 2n & 1, x × 2ⁿ → x « n, và x ÷ 2ⁿ (số không dấu) → x » n. Nên n % 2 == 0(n & 1) == 0 ra cùng một lệnh máy, cùng tốc độ. Trong Python (thông dịch) cả hai đều bị chi phí thông dịch lấn át. Bitwise đáng dùng vì CỜ/MẶT NẠ, gói dữ liệu, và để hiểu phần cứng - không phải để "qua mặt" %.

Vì ta làm trên 8-bit. ~0 trên số nguyên đầy đủ là -1 (bù hai, toàn bit 1), nhưng giới hạn 8 bit thì "toàn bit 1" = 1111 1111 = 255. Số bit của kiểu dữ liệu quyết định kết quả NOT.

Có. Bit bị đẩy ra ngoài bề rộng (8 bit) sẽ bị bỏ. Ví dụ 1100 0000 « 1 = 1 1000 0000 → cắt còn 1000 0000 (bit cao biến mất). Đó là tràn - giống giới hạn 8-bit của thanh ghi A ở mô hình CPU.

XOR cho 1 khi hai bit KHÁC nhau. Tính chất hay: x ^ x = 0, x ^ 0 = x, nên dùng để đảo bit có chọn lọc, tính parity/checksum, thậm chí hoán đổi hai biến không cần biến tạm. Rất hay gặp trong đồ hoạ, mã hoá đơn giản, phát hiện lỗi.

Khắp nơi: quyền tệp rwx (chmod 755), tập cờ tính năng, gói nhiều true/false vào một số (tiết kiệm bộ nhớ), tách kênh màu R/G/B từ một số màu, mặt nạ mạng (subnet mask). Một số nguyên = một dãy công tắc on/off.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Điểm khác biệt cốt lõi giữa phép toán trên bit (bitwise) và phép số học là gì?

  1. 1

    Đọc kết quả bằng tay

    Với A = 1011 0010, B = 0110 1100: tính A & B, A | B, A ^ B, và ~A (8-bit). Đối chiếu với công cụ ở Bước 2.

    Hoàn thành khi: A&B = 0010 0000; A|B = 1111 1110; A^B = 1101 1110; ~A = 0100 1101. Khớp công cụ.

  2. 2

    Dịch = nhân/chia

    Tính 5 « 3200 » 2 bằng tay; so với 5 × 8 và 200 ÷ 4.

    Hoàn thành khi: 5 « 3 = 40 = 5×8; 200 » 2 = 50 = 200÷4. Dịch trái nhân, dịch phải chia (luỹ thừa 2).

  3. 3

    Chẵn/lẻ bằng bit

    Bằng lời: vì sao chỉ cần nhìn BIT CUỐI là biết một số chẵn hay lẻ? Vì sao n & 1 cho cùng kết quả với n % 2?

    Hoàn thành khi: Bit cuối là giá trị 2⁰ = 1 - phần lẻ duy nhất; mọi bit khác là bội của 2 (chẵn). & 1 giữ đúng bit đó = số dư khi chia 2.

  4. 4

    Mặt nạ quyền

    Đặt READ=1, WRITE=2, EXEC=4. Cho quyen = READ | WRITE. Kiểm “có WRITE?” bằng &. Bỏ WRITE bằng & ~. Bật EXEC bằng |.

    Hoàn thành khi: quyen = 011; quyen & WRITE ≠ 0 (có); quyen & ~WRITE = 001; quyen | EXEC = 101.

  5. 5

    Tràn khi dịch

    Trên 8-bit: 1100 0000 « 1 ra bao nhiêu? Bit nào bị mất?

    Hoàn thành khi: 1000 0000 (128). Bit 1 ở vị trí 2⁷ bị đẩy ra ngoài 8 bit → mất.

  6. 6

    Trung thực về tốc độ

    Bằng lời: vì sao câu “n % 2 chậm hơn n & 1” là SAI trên trình biên dịch hiện đại?

    Hoàn thành khi: Compiler tự đổi % (và ×/÷) theo luỹ thừa 2 thành phép bit/dịch tương ứng → cùng một lệnh máy, cùng tốc độ.