The shelf that held 255 boxes
A warehouse program counts the boxes on each shelf. One evening a worker takes a box from a shelf the program thinks is empty.
The screen now says the shelf holds 255 boxes. Nothing is broken. The program stored -1 the only way it could, as eight bits, and the screen read those bits as a plain number.
By the end of this lesson
Note
I can store numbers and text as bits.
Key words
| Word | What it means | Example |
|---|---|---|
| Bit | one binary digit: 0 or 1 | |
| Byte | eight bits, the unit memory is counted in | |
| Binary | writing numbers with only the digits 0 and 1 | 11001000 |
| Hexadecimal | base 16, one digit for every four bits | 0xc8 |
| Two's complement | the usual way to store negative whole numbers in bits | |
| Character code | the number that stands for one letter or sign | 65 for A |
The cells students fill in are left blank in this preview.
Binary counts in twos
In ordinary numbers each place is worth ten times the place to its right. In binary each place is worth twice as much.
Eight places, one byte, are worth 128, 64, 32, 16, 8, 4, 2 and 1. Add the places that hold a 1. So 11001000 is 128 + 64 + 8, which is 200.
Worked example: 200 in one byte
| Place value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|---|
| Bit | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
| Counts | 128 | 64 | 0 | 0 | 8 | 0 | 0 | 0 |
The cells students fill in are left blank in this preview.
Go to the architecture folder
cd ~/archShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Ask Python for three conversions
python3 -c "print(bin(13), hex(255), int('1101', 2))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The shell writes hexadecimal too
printf '%d %x\n' 200 200Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
One hex digit is four bits
Hexadecimal has sixteen digits: 0 to 9, then a to f for ten to fifteen. Each hex digit stands for exactly four bits.
So a byte is always two hex digits. 200 is 1100 1000 in bits, which is c then 8, written 0xc8. Tools that show memory or machine code use hex because it is short and still lines up with the bits.
Negative numbers use the top bit
A byte has no minus sign. Two's complement makes the top bit worth -128 instead of 128. The other seven bits keep their usual values.
So 11111111 is -128 + 127, which is -1. And 10000000 is -128 on its own. To write a negative number, add 256 and write the result as a plain byte: -5 becomes 251, which is 11111011.
Worked example: one byte read two ways
| Bits | Read as unsigned | Read as two's complement |
|---|---|---|
| 00000101 | 5 | 5 |
| 01111111 | 127 | 127 |
| 10000000 | 128 | -128 |
| 11111011 | 251 | -5 |
| 11111111 | 255 | -1 |
The cells students fill in are left blank in this preview.
Write a RISC-V program that subtracts
printf '.globl _start\n_start:\n li a0, 2\n li a1, 3\n sub a0, a0, a1\n li a7, 93\n ecall\n' > minus.sShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Assemble it
riscv64-linux-gnu-as minus.s -o minus.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Link it
riscv64-linux-gnu-ld minus.o -o minusShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it on the emulator
qemu-riscv64 ./minusShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read the exit status
echo $?Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think the bits themselves say whether a number is negative. They do not. The program worked out 2 - 3 and left -1 in a0. Linux kept the low eight bits, 11111111, and the shell read them as unsigned: 255. The same bits mean -1 or 255 depending on who reads them.
Text is numbers too
A computer stores a letter as its character code. A is 65 and a is 97. The first 128 codes are the same in every common scheme.
UTF-8 stores those 128 in one byte each. Every other character takes two to four bytes. The letter é is code 233, and UTF-8 stores it as the two bytes c3 a9. So a word's length in bytes is not always its length in letters.
Character codes and UTF-8 bytes
python3 -c "print(chr(233).encode('utf-8'), ord('A'), ord('a'))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Fractions are stored approximately
A fraction such as 0.1 is stored as floating point: a sign, a power of two and a fraction, 64 bits in all. Most decimal fractions cannot be written exactly in powers of two, just as one third cannot be written exactly in decimals.
So 0.1 + 0.2 is very close to 0.3 but not equal to it. Never test money or counts with == on floating point.
Add 0.1 and 0.2
python3 -c "print(0.1 + 0.2)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The bytes of 1000, smallest first
python3 -c "print((1000).to_bytes(4, 'little').hex(' '))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further: widening a number
Write -20 in 8 bits by hand. Then write it in 16 bits. Compare the two: what has happened to the eight new bits on the left?
Check both with your to_bits function. Then say what a processor must do when it loads one signed byte into a 64-bit register.