Số âm được biểu diễn như thế nào?


Số âm được biểu diễn như thế nào?

Trải qua các môn học cơ bản, các bạn cũng biết được rằng một số (ở hệ thập phân) khi chuyển sang nhị phân sẽ thông qua công thức chia 2 lấy dư từ dưới lên. Như ví dụ dưới đây:
(15)10 = (00001111)2
Câu hỏi đặt ra: Để biểu diễn số -15, người ta làm cách nào?
Chúng ta cũng đã nghe qua cách số âm biểu diễn là số 1 ở đầu đại diện cho số âm, số 0 ở đầu đại diện cho số dương. Thế nhưng có 2 vấn đề xảy ra như sau:
1.     Dãy số 00001111 cũng bằng với 000…0001111, tức là có vô hạn số 0 ở bên trái, vậy đặt số 1 ở đâu cho hợp lí?
2.     Ta giả sử chỉ chấp nhận độ dài tối đa của dãy số là 00001111, vậy thì khi ta đặt 1 ở bit cực trái sẽ là 10001111, liệu đây có phải là số âm như ta đã biết?
Chúng ta hãy thử đổi hệ nhị phân của dòng trên sang thập phân xem nào:
(10001111)2= (143)10
Wait, khoan đã!? Tại sao nó không phải là -15, mà thật sự thì 10001111 hệ nhị phân chính là 143 hệ thập phân. Không lẽ không tồn tại số âm ư? Không đúng! Phải có một quy định nào đó về cách biểu diễn số âm, một quy tắc chọn ô đầu tiên làm dấu.
Phải, quy tắc đó sẽ tùy thuộc vào kiểu dữ liệu quy định nó. Chúng ta sẽ thử tưởng tượng 1 kiểu dữ liệu dùng 8 ô nhớ để lưu trữ. Với mỗi ô nhớ như vậy sẽ có 2 trường hợp: 0 và 1. Như thế sẽ có 2^8 = 256 cách biểu diễn con số này.

Bắt đầu từ con số (00000000)2 là số (0) 10 và kết thúc ở số (11111111)2 là số (255) 10. Vậy kích thước này biểu diễn dãy số trong phạm vi từ 0..255 với kích thước 1 byte (1 byte = 8 bit). Đó chính là kiểu dữ liệu byte trong Pascal mà mọi người đã học.
Tuy nhiên, với Java, kiểu byte lại nằm trong phạm vi từ -128..127, tức Java sẽ quy định 7 ô nhớ bên phải biểu diễn số, ô bit cực trái sẽ biểu diễn dấu âm hoặc dương. Vậy nghĩa là số âm sẽ dựa vào kiểu dữ liệu mà xây dựng nên các quy tắc phân chia giữa ô nhớ có dấu và các ô nhớ chứa chữ số. Vậy ở đây thì chúng ta có thể dùng được (10001111)2 trở thành (-15) 10 như đã được giải quyết ở trên rồi (tôi vẫn chưa nói là đúng nhé). Hey, chưa xong đâu bạn. Nếu bạn tinh ý sẽ nhận ra rằng số (00000000) 2 với số (10000000)10 không lẽ biểu diễn cho số 0 và -0 ư, như thế thì phạm vi của nó chỉ là -127..127 gồm 255 chữ số thôi chứ, vì có 2 trường hợp cùng biểu diễn số 0 rồi còn gì. Không lẽ số 128 dùng mã (10000000)2 để biểu diễn nhỉ. Ồ, chính xác thì số 128 dùng mã (10000000)2 có thể biểu diễn được (dùng công thức để kiểm tra), nhưng theo nguyên tắc thì bit cực trái đầu tiên thể hiện dấu cơ mà. Để ý phạm vi của byte là -128..127, vậy (10000000)2 biểu diễn cho -128 cũng hợp lý nhỉ. Ừ thì cũng đúng đó, nhưng khi bạn dãn con số ra với phạm vi 2 byte và chọn dấu ở bit cực trái như sau: (1000 0000 0000 0000)2, vậy thì nó còn biểu diễn cho -128 nữa không nhỉ? Có vấn đề đấy, vì nếu theo như giải thích hồi nãy với kích thước 2 byte thì (0000 0000 1000 0000)2 biểu diễn số 128 thì (1000 0000 1000 0000)2 cũng biểu diễn được số -128 thôi. Vậy chúng ta đã hiểu sai về cái gì ư?
Vâng! Đó là cách người ta biểu diễn số âm. Lấy ví dụ với số 15, khi ta biết được nó biểu diễn với phạm vi 1 byte như sau (00001111)2, thì ta biểu diễn số âm theo nguyên tắc gọi là “bù 2” (tiếng Anh gọi là Two’s Complement). Theo quy tắc này, ta thực hiện chuyển đổi như sau.
B1: Đảo tất cả các bit lại từ 1 thành 0, từ 0 thành 1. Cụ thể, (00001111)2 sẽ trở thành (11110000)2
B2: Cộng dãy số vừa nhận được cho 1. Cụ thể (11110000)2 +(1)2 = (11110001)2.
Và dãy số (11110001)2 chính là biểu diễn cho số -15. Ta da, vậy là vấn đề đã được giải quyết ổn thỏa. Hãy thử trường hợp với số 128 để xem số -128 biểu diễn thế nào. Mà khoan, số 128 phá vỡ quy tắc về dấu rồi thì sao mà chuyển đây. Vậy chúng ta hãy dựa theo khoảng cách giữa các số âm mà tìm ra nhé!
Ta có số -15 là (11110001)2, số -14 thông qua tính toán như trên sẽ là  (11110010)2. Ồ, thế là số càng tiến về âm vô cùng thì sẽ càng trừ đi 1 đơn vị (vì ta thấy số -14 (11110010)2 – (1)2 sẽ là số -15 (11110001)2). Vậy với -127 là (10000001)2 thì -128 sẽ là (10000000)2. Vậy chúng ta thử thêm 1 lần nữa với phép “bù 2”, kích thước 2 byte cho số -128 từ 128 xem nào (nhớ là bit cực trái làm dấu nhé). Wow, chúng ta thu được một kết quả mới mẻ, đó là (1111 1111 1000 0000)2. Thế thì chúng ta hiểu rằng 10000000 cũng là 1111 1111 1000 0000, tức là số âm thì số 1 có thể kéo dài vô tận, còn số dương thì số 0 kéo dài vô tận. Hoho, vậy là với quy tắc bù 2 này thì việc casting với kiểu dữ liệu có kích thước lớn hơn sẽ không là vấn đề gì. Thế là mọi chuyện được giải quyết ổn thỏa.
P/s: Cuối cùng cũng được ăn cơm, phù
Nội dung bài viết thuộc về Lê Công Diễn.

Người viết: Lê Công Diễn
Mang đi nhớ ghi nguồn

Nhận xét

Đăng nhận xét

Bài đăng phổ biến từ blog này

Deploy project Springboot MIỄN PHÍ sử dụng Render

Ứng dụng Mã hóa bất đối xứng (Asymmetric cryptography) vào Chữ ký số (Digital Signature)

API và HTTP - Một số khái niệm cơ bản cần biết về Web (Phần 2)