Mọi con số trong máy tính đều là một dãy bit. Con người hiếm khi đọc trực tiếp dãy đó: ta đọc hệ thập lục phân (hệ 16), một cách viết tắt biến bốn bit thành một kí tự. Số âm thêm một lớp nữa là phép bù hai, và chính nó giải thích vì sao cùng một byte có thể là 255 hoặc −1 tuỳ người đọc. Bài này làm tay từng bước với số thật, rồi liệt kê những lỗi gây nhầm lẫn nhiều nhất. Mọi kết quả dưới đây đều kiểm tra được bằng công cụ đổi cơ số.
Cơ số chỉ là kích cỡ của một bó
Trong hệ thập phân, mỗi vị trí có giá trị gấp mười lần vị trí bên phải: 156 là 1 × 100 + 5 × 10 + 6. Trong hệ 2, mỗi vị trí gấp đôi vị trí bên phải, nên các vị trí có giá trị 1, 2, 4, 8, 16, 32, 64, 128 và cứ thế. Trong hệ 16 chúng có giá trị 1, 16, 256, 4096. Bản thân con số không đổi, chỉ có cỡ bó thay đổi.
Hệ 16 cần mười sáu kí hiệu chữ số, nên sau 9 là A (10), B (11), C (12), D (13), E (14) và F (15). Các cơ số đến 36 tiếp tục theo bảng chữ cái tới Z (35), vì thế hệ 36 đôi khi được dùng để tạo mã định danh ngắn.
Đổi thập phân sang nhị phân bằng phép chia liên tiếp
Để viết 156 ở hệ 2, chia cho 2 và giữ lại số dư:
| Bước | Thương | Số dư |
|---|---|---|
| 156 ÷ 2 | 78 | 0 |
| 78 ÷ 2 | 39 | 0 |
| 39 ÷ 2 | 19 | 1 |
| 19 ÷ 2 | 9 | 1 |
| 9 ÷ 2 | 4 | 1 |
| 4 ÷ 2 | 2 | 0 |
| 2 ÷ 2 | 1 | 0 |
| 1 ÷ 2 | 0 | 1 |
Đọc các số dư từ dưới lên: 1001 1100. Kiểm tra theo chiều ngược lại: 128 + 16 + 8 + 4 = 156. Cách này dùng được cho mọi cơ số: chia 156 cho 16 được thương 9, dư 12, nên 156 là 9C trong hệ 16.
Vì sao lập trình viên đọc hệ 16 chứ không đọc nhị phân
Mười sáu là 2⁴, nên một chữ số hệ 16 ứng với đúng bốn bit, không hơn không kém. Đổi giữa nhị phân và hệ 16 không cần tính toán gì: tách các bit thành nhóm bốn từ phải sang và thay mỗi nhóm bằng một chữ số.
- 1001 1100 → 1001 là 9, 1100 là 12 (C) → 9C
- 0x1E90FF → 0001 1110 1001 0000 1111 1111
Một byte có tám bit nên luôn là đúng hai chữ số hệ 16, từ 00 đến FF. Đó là lí do màu được viết như #1E90FF (đỏ 0x1E = 30, lục 0x90 = 144, lam 0xFF = 255), địa chỉ bộ nhớ và chữ kí tệp được hiển thị ở hệ 16, và mã băm SHA-256 dài 64 kí tự hệ 16 (256 bit ÷ 4). Hệ thập phân không khớp với bit: 255 và 256 trông chẳng liên quan gì, nhưng FF và 100 cho thấy ngay ranh giới byte.
Hệ bát phân (hệ 8) gom bit theo nhóm ba. Nó còn tồn tại chủ yếu ở quyền tệp trên Unix: 755 là 111 101 101, tức đọc, ghi, chạy cho chủ sở hữu và đọc, chạy cho người khác.
Bù hai: cách lưu số âm
Một số lượng bit cố định cho một số lượng mẫu cố định: 8 bit cho 256 mẫu. Đọc như số không dấu, chúng là 0 đến 255. Để có số âm, quy tắc bù hai nói: mọi mẫu có bit cao nhất bằng 1 biểu diễn giá trị không dấu của nó trừ 256. Vì vậy một byte có dấu chứa từ −128 đến 127.
Công thức viết số âm bằng tay là "đảo bit rồi cộng một". Lấy −42 với 8 bit:
- 42 ở hệ 2 là 0010 1010.
- Đảo mọi bit: 1101 0101.
- Cộng một: 1101 0110, tức 0xD6.
Kiểm tra: 0xD6 là 214 khi không dấu, và 214 − 256 = −42. Hay hơn nữa, cộng hai mẫu với nhau: 0010 1010 + 1101 0110 = 1 0000 0000. Bit thứ chín rơi ra ngoài byte và phần còn lại là 0, đúng như 42 + (−42). Đó chính là ý nghĩa của bù hai: bộ xử lí cộng số có dấu và không dấu bằng cùng một mạch.
Hai hệ quả nữa đáng biết:
- Tràn số thì quay vòng. 127 + 1 trong một byte có dấu là 0111 1111 + 1 = 1000 0000, tức −128. Phần lớn ngôn ngữ lập trình không hề cảnh báo.
- Mở rộng thì chép bit dấu. −42 dưới dạng số 16 bit là 0xFFD6, không phải 0x00D6. Chép các bit 1 vào những bit cao mới (mở rộng dấu) giữ nguyên giá trị −42; điền số 0 (mở rộng không) sẽ biến nó thành 214.
Dãy bit của một phân số thập phân
Phần lẻ theo cùng quy tắc nhưng ngược chiều: mỗi vị trí sau dấu phẩy có giá trị bằng nửa vị trí trước (½, ¼, ⅛…). Một phân số chỉ có dạng nhị phân ngắn khi mẫu số là luỹ thừa của 2. 0,5 là 0,1 ở hệ 2, 0,75 là 0,11, nhưng 0,1 là 1/10 mà 10 có thừa số 5, nên các chữ số nhị phân của nó không bao giờ dừng: 0,0(0011), khối 0011 lặp mãi. Một số thực 32 bit phải dừng ở đâu đó, nên 0,1 được lưu thành 0,100000001490116119384765625 (bit 3DCCCCCD). Thẻ số thực của công cụ hiển thị mọi chữ số của giá trị được lưu và độ lệch so với số bạn nhập.
Những lỗi hay gặp
- Đọc hệ 16 như hệ 10. "10" ở hệ 16 là mười sáu. Khi một giá trị không có tiền tố, hãy hỏi nó thuộc cơ số nào; 0x10, 10h và 10₁₆ đều là mười sáu.
- Quên độ rộng. Câu hỏi "−1 ở hệ 2 là gì?" không có đáp án cho tới khi bạn nói bao nhiêu bit: 1111 1111 với 8 bit, mười sáu số 1 với 16 bit. Cùng mẫu FF là 255 hay −1 tuỳ kiểu dữ liệu có dấu hay không.
- Hiểu sai khoảng giá trị. 128 không vừa một byte có dấu, còn −128 thì vừa. Sự bất đối xứng là có thật: số âm nhiều hơn số dương một số.
- Mở rộng không cho số âm. Chuyển một byte có dấu sang số nguyên rộng hơn mà điền 0 ở đầu sẽ lặng lẽ biến −42 thành 214.
- Số 0 đứng đầu nghĩa là bát phân. Trong C, 0777 là số bát phân (511 hệ 10). Python 3 báo lỗi và đòi viết 0o777, còn
parseInt("0777")của JavaScript đọc thành 777. Hãy viết đúng tiền tố bạn muốn. - Tưởng 0,1 là chính xác. Không có số thực nhị phân nào lưu đúng 0,1. Hãy so sánh số thực với một sai số cho phép, hoặc dùng số thập phân hay số nguyên khi tính tiền.
Thử ngay
Gõ 156 vào công cụ đổi cơ số để thấy 1001 1100 và 9C, chuyển sang thẻ bù hai và nhập −42 để xem từng bước đảo bit cộng một, rồi mở thẻ số thực để thấy vì sao 0,1 + 0,2 không đúng bằng 0,3. Máy tính khoa học lo phần tính toán xung quanh.