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
1000 tym cho chủ bài ạ
Trả lờiXóaMấy cái này hại não quá, huhu!!
Trả lờiXóaNội dung comment thuộc về rokvikings09.