Trong bài này mình sẽ giới thiệu với các bạn một số khái niệm về kiểu số cơ bản thường sử dụng trong lập trình nhúng
Mục tiêu
- Hiểu được số nhị phân (binary), số thập lục phân(hexadecimal), kiểu số nguyên có dấu và không dấu (signed và unsigned integers)
- Cách chuyển đổi giữa các kiểu số
Giới thiệu
Chúng ta đã quá quen với hệ thập phân, nghĩa là các số với giá trị là {0,1,2,3,4,5,6,7,8,9}, hệ nhị phân cũng tương tự nhưng chỉ có 2 giá trị là {0,1} và hệ thập lục phân sẽ sử dụng các số có giá trị là {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F}, ở đây mình xin đi qua một số ví dụ để các bạn có thể nắm được một số cách chuyển đổi cơ bản.
Hệ thập phân:
1984 = 1•103 + 9•102 + 8•101 + 4•100
273.15 = 2•102 + 7•101 + 3•100 + 1•10-1 + 5•10-2
Hệ nhị phân:
011010102 = 0•27 + 1•26 + 1•25 + 0•24 + 1•23 + 0•22 + 1•21 + 0•20 = 64+32+8+2 = 106
Hệ thập lục phân:
0x12AD = 1•163 + 2•162 + 10•161 + 13•160 = 4096+512+160+13 = 4781
Chuyển đổi giữa số nhị phân và thập phân
Hầu hết các số nhị phân được lưu trong bộ nhớ của máy tính sẽ có độ dài là 8, 16, 32 bit. 8-bit được gọi là 1 byte, 16-bit thì được gọi là halfword và 32-bit được gọi là word.
Chuyển từ nhị phân sang thập phân
Ví dụ: Giá trị thập phân của số nhị phân 8 bit 111111112 là bao nhiêu ?
111111112 = 1•27 + 1•26 + 1•25 + 1•24 + 1•23 + 1•22 + 1•21 + 1•20 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255
Ví dụ: Giá trị thập phân của số nhị phân 8 bit 011010102
011010102 = 0•27 + 1•26 + 1•25 + 0•24 + 1•23 + 0•22 + 1•21 + 0•20 = 64+32+8+2 = 106
Chuyển từ thập phân sang nhị phân
Ví dụ: Giá trị nhị phân của số thập phân 123
123/2 = 61 dư 1
61/2 = 30 dư 1
30/2 = 15 dư 0
15/2 = 7 dư 1
7/2 = 3 dư 1
3/2 = 1 dư 1
1/2 = 0 dư 1
Giá trị số nhị phân là 01111011
Ví dụ: Giá trị nhị phân của số thập phân 254
254/2 = 127 dư 0
127/2 = 63 dư 1
63/2 = 31 dư 1
31/2 = 15 dư 1
15/2 = 7 dư 1
7/2 = 3 dư 1
3/2 = 1 dư 1
3/2 = 1 dư 1
Giá trị số nhị phân là 11111110
Chuyển đổi giữa số nhị phân và thập lục phân
Số nhị phân là ngôn ngữ mặc định của máy tính, nhưng lại gây quá nhiều bối rối cho chúng ta, bạn thử hình dung xem nếu với 1 dãy dữ liệu toàn 010010101010101010101 như vậy thì có đáng sợ không ? Để đơn giản khi làm việc với các số nhị phân thì người ta sử dụng các số có liên quan gọi là số thập lục phân (hexadecimal).
Với số thập lục phân sẽ có giá trị là các số {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F}. Trong các máy tính thì không phân biệt khi viết hoa hoặc viết thường các chữ cái trong số thập lục phân nên {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F} và {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f} được xem là như nhau.
Chúng ta thường gọi số thập lục phân là số “hex” và để phân biệt với các số khác thì thường có 0x hoặc $ ở phía trước số thập lục phân, trong C thường dùng là 0x. Các số thập lục phân cơ bản 160, 161, 162, 163,… hoặc 1, 16, 256, 4096,…
Nibble thường được dùng để định nghĩa 4 bit nhị phân hoặc 1 số thập lục phân (16 = 24), mỗi 4-bit nibble này sẽ tương ứng với 1 số hex như hình sau
Để chuyển từ số nhị phân(Binary) về thập lục phân(Hexadecimal) và ngược lại thì chúng ta sẽ xem hình minh họa sau
Chuyển từ thập lục phân sang nhị phân
Bước 1: Chuyển từng số trong số hex sang nibble.
Bước 2: Kết hợp các nibble lại thành số nhị phân.
Ví dụ: Chuyển số hex 0x40 về số nhị phân
Đầu tiên chia từng số hex ra và chuyển về số nhị phân 0x4 = 01002 và 0x0 = 00002 . Sau đó kết hợp 2 số này lại thành 1 là 010000002 . Vậy 0x40 = 010000002
Ví dụ: Chuyển số hex 0x63F về số nhị phân
Đầu tiên chia từng số hex ra và chuyển về số nhị phân 0x6 = 01102 và 0x3 = 00112 ,0xF = 11112 . Sau đó kết hợp 3 số này lại thành 1 là 0110001111112 . Vậy 0x63F = 0110001111112
Chuyển từ nhị phân sang thập lục phân
Bước 1: Chia các số nhị phân thành các nibble hợp lý.
Bước 2: Chuyển các nibble này thành số hex tương ứng.
Ví dụ: Chuyển số nhị phân 010001012 về số hex
Đầu tiên chia số nhị phân thành nhóm 4 bit 01002 = 0x4 và 01012 = 0x5. Tiếp tục kết hợp 2 số này lại thành 1 là 0x45. Vậy 010001012 = 0x45
Ví dụ: Chuyển số nhị phân 1100101010112 về số hex
Đầu tiên chia số nhị phân thành nhóm 4 bit 11002 = 0xC và 10102 = 0xA, 10112 = 0xB. Tiếp tục kết hợp 3 số này lại thành 1 là 0xCAB. Vậy 1100101010112= 0xCAB
Vậy giá trị thập phân của số thập lục phân 8 bit 0xFF là bao nhiêu ?
0xFF = 15•161 + 15•160 = 255
Precision và Byte
Precision có ý nghĩa là số giá trị riêng biệt hoặc các giá trị khác biệt, nó được biểu thị dưới dạng alternative(số thay thế), byte, hoặc binary bit (bit nhị phân).
Ví dụ: Định dạng của một số 8-bit có thể biểu diễn được 256 số khác nhau. Cụ thể với 8-bit của DAC(chuyển đổi số tương tự) có thể tạo ra được 256 giá trị analog output hoặc với 8-bit ADC(chuyển đổi tương tự số) thì có thể đo được 256 giá trị khác nhau từ analog input.
Ta có thể xem bảng sau để biết được mối quan hệ giữa precision trong bit nhị phân và trong alternative. Ký hiệu [[x]] được định nghĩa là giá trị integer lớn nhất. Cột byte là số byte mà bộ nhớ sử dụng để lưu trữ
Ví dụ:[[2.1]], [[2.9]] và [[3.0]] sẽ bằng 3.
Một byte sẽ bao gồm 8-bit với từng bit từ b7,…,b0 là các số nhị phân có giá trị là 1 hoặc 0. b7 thường được gọi là most significant bit (MSB) hay còn gọi là bit có trọng số lớn nhất, và b0 là least significant bit (LSB) hay bit có trọng số nhỏ nhất.
Nếu 1 byte được dùng để biểu diễn số unsigned thì giá trị của số này là
N = 128•b7 + 64•b6 + 32•b5 + 16•b4 + 8•b3 + 4•b2 + 2•b1 + b0
Có một lưu ý là trọng số của bit n là 2n và có tới 256 số unsigned 8-bit khác nhau, số nhỏ nhất là 0 và số lớn nhất là 255.
Ví dụ: 000010102 là 8+2 hoặc 10
Ngoài ra với LSB thì ta có thể biết được một số là số chẵn(even) hay lẻ (odd)
Ví dụ:
2’s Complement
Trước khi bắt đầu thì có một số điểm như sau:
- Nếu bit nhị phân trọng số thấp là 0 thì đó là số chẵn.
- Nếu bit ngoài cùng bên phải của n bit là 0 thì số thì số đó chia hết cho 2n.
- Với một số 8-bit unsigned, nếu bit 7 là low (0) thì số đó sẽ có giá trị từ 0 – 127, và bit 7 là high (1) thì số sẽ có giá trị từ 128 – 255.
Một trong những cách đầu tiên để biểu diễn số signed được gọi là one’s complement. Với cách này thì sẽ thực hiện đảo toàn bộ bit để có được số âm.
Ví dụ: Nếu số 25 =000110012 thì số -25 = 111001102. Do đó một số 8-bit one’s complement sẽ có giá trị từ -127 tới +127. MSB sẽ là bit sign (bit có dấu) bằng 1 nếu đó là số âm. Khó ăn ở chỗ là nếu dùng dạng như này thì sẽ có 2 số 0 là +0 = 000000002, và –0 = 111111112. Điều này thì không logic cho lắm. Ngoài ra vấn đề quan trọng là số one’s complement không có dạng cơ bản. Two’s complement ra đời để khắc phục điều này.
Với two’s complement cách thực hiện giống với one’s complement, nhưng cộng thêm 1.
Ví dụ: Nếu số 25 =000110012 thì -25 = 111001112.
Vậy giá trị của một số signed 8-bit two’s complement là
N = -128•b7 + 64•b6 + 32•b5 + 16•b4 + 8•b3 + 4•b2 + 2•b1 + b0
Ta có thể xác định được các thành phần cơ bản của số 8-bit signed là {-128, 64, 32, 16, 8, 4, 2, 1}.
Chúng ta tiếp tục qua một số ví dụ nữa xem sao
Ví dụ: Giá trị của số 11100111 dưới dạng signed và unsigned integer
Giá trị Unsigned = 1 *27 + 1 *26 + 1 *25 + 0 *24 + 0 *23 + 1 *22 + 1 *21 + 1 *20 = 231
Giá trị Signed = -1 *27 + 1 *26 + 1 *25 + 0 *24 + 0 *23 + 1 *22 + 1 *21 + 1 *20 = -25
Chuyển đổi số 45 decimal về 8-bit binary và hexadecimal
45=32+8+4+1, nên 45 = 001011012 = 0x2D.
Chuyển đổi số 200 decimal về 8-bit binary và hexadecimal
200=128+64+8, nên 200 = 110010002 = 0xC8.
Chuyển số signed 110110102 về decimal.
-128+64+16+8+2 = -38.
Giá trị của số signed và unsigned là khác nhau do MSB, dựa vào giá trị này thì máy tính không thể nói cho chúng ta biết được đó là số signed hay unsigned, do đó thì người lập trình phải biết được điều này bằng cách track sử dụng biến dữ liệu 32-bit kiểu long
Words và Halfwords
Một halfword hoặc double byte gồm 16 bits, với mỗi bit b15,…,b0 là số nhị phân có giá trị là 0 hoặc 1.
Nếu halfword được dùng để biểu thị số unsigned thì giá trị của nó là
N = 32768•b15 + 16384•b14 + 8192•b13 + 4096•b12+ 2048•b11 + 1024•b10 + 512•b9 + 256•b8+ 128•b7 + 64•b6 + 32•b5 + 16•b4 + 8•b3 + 4•b2 + 2•b1 + b0
Có 65536 giá trị khác nhau của số 16-bit unsigned. Giá trị nhỏ nhất là 0 và lớn nhất là 65535.
Các thành phần cơ bản của số 16-bit unsigned là {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768}
Ví dụ: Giá trị của 00100001100001002 hoặc 0x2184 là 8192+256+128+4 hoặc 8580.
Nếu halfword được dùng để biểu thị số signed thì giá trị của nó là
N = -32768•b15 + 16384•b14 + 8192•b13 + 4096•b12+ 2048•b11 + 1024•b10 + 512•b9 + 256•b8+ 128•b7 + 64•b6 + 32•b5 + 16•b4 + 8•b3 + 4•b2 + 2•b1 + b0
Có 65536 giá trị khác nhau của số 16-bit unsigned. Giá trị nhỏ nhất là –32768 và lớn nhất là 32767.
Các thành phần cơ bản của số 16-bit unsigned là {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, -32768}
Ví dụ: Giá trị của 11010000000001002 hoặc 0xD004 là –32768+16384+4096+4 hoặc –12284.
Lỗi thường gặp khi bạn sử dụng các phép toán 16-bit trên các số 8-bit hoặc sử dụng các phép toán 8-bit trên các số 16-bit
Một word trong ARM Cortex-M sẽ gồm 32 bits. Giống như số unsigned với 32 bit thì mỗi bit b31,…,b0 sẽ là số nhị phân và có giá trị 1 hoặc 0.
Nếu số 32-bit được dùng để biểu thị số unsigned integer, thì giá trị của số này là:
N = 231 • b31 + 230 • b30 + … + 2•b1 + b0 = sum(2i • bi) for i=0 to 31
Có 232 số unsigned 32-bit khác nhau. Số nhỏ nhất là 0 và lớn nhất là 232-1. Ngưỡng của nó sẽ nằm trong khoảng từ 0 tới 4 tỷ. Các thành phần cơ bản là {1, 2, 4, … , 229, 230, 231}
Nếu số 32-bit được dùng để biểu thị số singed thì giá trị của nó là
N = -231 • b31 + 230 • b30 + … + 2•b1 + b0 = -231 • b31 + sum(2i • bi) for i=0 to 30
và các thành phần cơ bản {1, 2, 4, … , 229, 230, -231}
Khi lập trình C thì kiểu dữ liệu ta thường dùng là char short và long để xác định các số 8-bit,16-bit hoặc 32-bit, nhưng với hầu hết complier của ARM thì int là 32 bit.
Tạm Kết
Vậy là xong được một số khái niệm và các ví dụ minh họa về hệ số và các cách chuyển đổi giữa các số nhị phân, thập phân, thập lục phân, các khái niệm về precision và byte, 2’s Complement với signed và unsigned, word và halfword