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.
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:
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:
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.
Vậy biểu diễn ở trên là bù 2 của 15.
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ã:
Và sau đây là đầu ra:
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.
Đây là đầu ra bây giờ:
Ồ, đầ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:
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:
Sau đó cộng 1
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.
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
Mã:
11111111 11111111 11111111 11110000
Mã:
11111111 11111111 11111111 11110001
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;
}
Mã:
a = -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;
}
Mã:
a=4294967281
Đú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
Mã:
00000000 00000000 00000000 00001110
Mã:
00000000 00000000 00000000 00001111