Hướng dẫn lập trình C trên Linux Phần 15 - Số bù 2 và số âm

theanh

Administrator
Nhân viên
Cho đến nay, trong loạt bài hướng dẫn lập trình C đang diễn ra này, chúng ta đã thảo luận khá nhiều khái niệm, nhưng lại bỏ sót một khái niệm cơ bản. Đó là về số âm. Vâng, mặc dù chúng tôi đã đề cập ngắn gọn về biến có dấu và không dấu trong một trong những bài hướng dẫn ban đầu của mình, nhưng chúng tôi thực sự không thảo luận về cách lưu trữ số âm trong bộ nhớ.

Vâng, đó chính xác là những gì sẽ được thảo luận trong bài hướng dẫn này. Vậy thì không cần phải nói thêm nữa, chúng ta hãy bắt đầu với phần thảo luận.

Bổ sung 2​

Trước khi bắt đầu với phần giải thích về cách biểu diễn số âm trong bộ nhớ, điều quan trọng là chúng ta phải biết khái niệm về bổ sung 1 và 2, cả hai đều là phép toán cấp nhị phân.

Chúng ta hãy lấy một ví dụ rất đơn giản. Giả sử bạn có một số nguyên 4 byte 'a' với giá trị thập phân là 15. Sau đây là cách nó được biểu diễn trong bộ nhớ dạng nhị phân:
Mã:
00000000 00000000 00000000 00001111
Bây giờ, để tính toán bổ sung 1, chỉ cần đảo ngược tất cả các bit. Sau đây là biểu diễn bù 1 của 15:
Mã:
11111111 11111111 11111111 11110000
Bây giờ, nếu bạn thêm 1 vào biểu diễn nhị phân ở trên, bạn sẽ nhận được bù 2.
Mã:
11111111 11111111 11111111 11110001
Vậy biểu diễn ở trên là bù 2 của 15.

Số âm​

Bây giờ, một số bạn có thể đang nghĩ tại sao chúng ta lại thảo luận về bù 1 và bù 2? Câu trả lời nằm ở thực tế là biểu diễn nhị phân của một số âm được tính toán thông qua bù 2.

Khó tin phải không? Đây là bằng chứng:

Phần bù 2 mà chúng ta tính toán ở phần trước có thể được biểu diễn dưới dạng thập lục phân là 0xFFFFFFF1. Bây giờ, chúng ta hãy xem giá trị này ở dạng thập phân như thế nào thông qua chương trình C

Đây là mã:
Mã:
#include 

int main()
{
 int a = 0xFFFFFFF1;
 printf("a = %d", a);

 return 0;
}
Và sau đây là đầu ra:
Mã:
a = -15
Giờ thì bạn đã tin chưa? Chúng tôi bắt đầu với một số '15', tính toán phần bù 2 của nó và khi chúng tôi chuyển đổi giá trị phần bù 2 một lần nữa thành số thập phân, chúng tôi thấy rằng nó là -15.

Tiếp tục, bây giờ chúng ta hãy chỉnh sửa một chút mã để đảm bảo lệnh gọi printf đọc giá trị của biến 'a' dưới dạng một số nguyên không dấu.
Mã:
#include 

int main()
{
 int a = 0xFFFFFFF1;
 printf("a = %u", a);

 return 0;
}
Đây là đầu ra bây giờ:
Mã:
a=4294967281
Ồ, đầu ra đã thay đổi, và bây giờ nó là một giá trị dương rất lớn. Nhưng tại sao điều này lại xảy ra? Không phải 0xFFFFFFF1 là bù 2 của 15 như chúng ta đã thấy trước đó sao?

Đúng vậy, 0xFFFFFFF1 là bù 2 của 15, nhưng nếu bạn không nhìn theo góc độ đó, thì nó cũng là một giá trị bình thường (4294967281). Sự khác biệt nằm ở cách đọc nó. Nếu nó được đọc như một số nguyên có dấu (thông qua %d trong printf), bạn sẽ thấy đầu ra là -15, nhưng nếu nó được đọc như một số nguyên không dấu (thông qua %u trong printf), bạn sẽ thấy đầu ra là 4294967281.

Theo nguyên tắc chung với các biến có dấu (xử lý cả giá trị âm và giá trị dương), hãy nhớ rằng biểu diễn nhị phân của các số âm luôn có '1' là bit ngoài cùng bên trái, trong khi trong trường hợp các số dương, bit đang xét luôn là 0.

Cuối cùng, lưu ý rằng bạn cũng có thể đảo ngược biểu diễn bù hai để có được phần bù dương tương ứng. Ví dụ, chúng ta hãy lấy lại giá trị 0xFFFFFFF1, là biểu diễn hex của -15. Nó được biểu diễn dưới dạng nhị phân như sau:
Mã:
11111111 11111111 11111111 11110001
Bây giờ, để có được số dương tương ứng, chỉ cần thực hiện lại phép bù 2. Nghĩa là, trước tiên hãy thực hiện phép bù 1:
Mã:
00000000 00000000 00000000 00001110
Sau đó cộng 1
Mã:
00000000 00000000 00000000 00001111
Bây giờ, nếu bạn chuyển đổi số này, bạn sẽ nhận được giá trị 15 ở dạng thập phân.

Kết luận​

Tôi hy vọng hướng dẫn này đã giúp bạn hiểu được khái niệm về số âm trong bối cảnh cách chúng được biểu diễn trong bộ nhớ. Tôi khuyên bạn nên thử các ví dụ chúng tôi sử dụng trong hướng dẫn này và trong trường hợp bạn gặp bất kỳ vấn đề nào, hoặc có bất kỳ nghi ngờ hoặc thắc mắc nào, hãy để lại bình luận bên dưới.
 
Back
Bên trên