Lecture 05 - Representing numbers
Goals
- Learn how to represent numbers at our current level of abstraction of wires and logic
- Learn how to convert between bases
We now have the ability to create circuits that can carry out basic logic functions for us. But most of the work that we do with computers isn’t about logic prepositions. We all know that the proper use for a computer is sharing cat videos on the internet. Somehow we need videos, and images, and sound, and text, and numbers.
We will start with numbers because they are the easiest (and in truth everything else is built on top of them)
Base two (binary)
When I was writing out truth tables, I said that I was counting in binary – let’s get a little more formal
Binary is a base 2 number system, as opposed to our more common decimal, which is base ten.
What does it mean for a number system to be base-n?
There are a couple of ways that we can think about it - each digit in a number can have up to n different representations (e.g., base-2 (0,1), base-10 (0,1,2,3,4,5,6,7,8,9)) - more importantly, digits are positional and multiplied by powers of the base
so, for a number in base-b, we can convert to decimal using this formula \[(d_{3}d_{2}d_{1}d_{0})_{b} = (d_{3} * b^{3}) + (d_{2} * b^{2}) + (d_{1} * b^{1}) +(d_{0} * b^{0}) )\]
binary to decimal
In base-2 this is pretty straightforward since all of the multiplication is by 1s and 0s. So all you need to know is your powers of two.
decimal to binary
this is a little harder
We can start with a slightly different form of the equation above: Horner’s equation
We get here by just factoring out bs until we don’t have anything raised to a power
\[(d_{3}d_{2}d_{1}d_{0})_{b} = (((d_{3} * b) + d_{2}) * b + d_{1}) * b + d_{0}\] If we were to multiply the b s back in, we would get back to our original equation
If we take this expression and divide it by b, we will get
\[(((d_{3} * b) + d_{2}) * b + d_{1}), \text{remainder } d_{0}\]
\[(((d_{3} * b) + d_{2}) * b + d_{1}) / b = ((d_{3} * b) + d_{2}), \text{remainder } d_{1}\]
\[((d_{3} * b) + d_{2}) / b = d_{3}, \text{remainder } d_{2}\]
\[(d_{3} * b)/b = 0, \text{remainder } d_{3}\]
this provides our algorithm - reserve a spot to write the number in base b - while number is not equal to 0 - divide number by b getting a quotient and a remainder - add the remainder to the left of the new number - set number to the quotient - you should now have the number in base b representation
This will work in any base, but obviously the math is easier for binary
base 16 (hexadecimal)
A quick aside. Binary numbers get loooooong (and programmers are lazy), but bit patterns are important
So, we like bases other than base-10, like hex and octal (base-8)
The advantage to these bases is that 8 and 16 are powers of 2, which makes conversion between the bases easier
In base 16, there are 16 different values for each digit. In base-2, we can represent with all patterns of four bits
| base-2 | base-16 |
|---|---|
| 0000 | 0 |
| 0001 | 1 |
| 0010 | 2 |
| 0011 | 3 |
| 0100 | 4 |
| 0101 | 5 |
| 0110 | 6 |
| 0111 | 7 |
| 1000 | 8 |
| 1001 | 9 |
| 1010 | A |
| 1011 | B |
| 1100 | C |
| 1101 | D |
| 1110 | E |
| 1111 | F |
The big win here is that we only need to know how to convert between 1 digit of hex and 4 digits of binary and we can do any arbitrary conversion
\[A3_{16} = 10100011_2\]
We will typically write hex out as 0xA3
Doing math
Adding together numbers in binary works just like it does in decimal – we just have to carry out quicker
it is time to get back to our computer abstraction – how do we do this with circuits?
Let’s start with adding two 1 bit numbers together (one column of our addition)
We can make a truth table
| A | B | sum1 | sum0 | |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | |
| 0 | 1 | 0 | 1 | |
| 1 | 0 | 0 | 1 | |
| 1 | 1 | 1 | 0 |
Now, we can look at each output individually - sum0 is just an XOR - sum1 is just AND
We can build this pretty easily – we call it a half adder.
What if we want to add numbers with more bits? If we rename the outputs of the half adder to (carry_out, sum), we can use it for the first column of the addition.
What about the next column over? This one is a little different because it has a carry_in as well, so we can’t just add in a half adder
We need a new circuit, called a full adder that has three inputs (A, B, carry_in) and two outputs (sum, carry_out) - we could design this by starting with a new truth table - or you could think abstractly – the sum should come from A + B + carry_in, which we could rewrite as (A + B) + carry_in , and we know how to add two one bit numbers together (carry_out will still take some thought)
if we had a full adder and a half adder, we could chain them together to add any size number we like - e.g., to add two 8-bit numbers together we would need 7 full adders and one half adder
this would give us something called a ripple-carry adder > [!note] > > This is not actually how the adder in your computer works. This one is slow because you need to wait for the carrys to propagate. There are much faster adders that trade space for speed and precompute different variations of the answer simultaneously
Mechanical level
Skills - convert from binary to hex - convert from hex to binary