C.S. Basics: From Decimal to Binary And Back Again

Andrew (he/him) - Apr 1 '19 - - Dev Community

or, Understanding Binary and Hexadecimal Numbers


Binary and hexadecimal numbers can be daunting for beginner computer science students, or anyone unfamiliar with the inner workings of a computer. News reports and viral YouTube videos sometimes use numbers in these different bases to scare or intimidate people, suggesting that the content is over their heads or requires advanced hacking skills™ to understand.

But binary and hexadecimal numbers are easy if you have a good foundation to build on. The easiest way to understand binary numbers is by starting with what you already know about the familiar decimal (base-10) numbers and building from there. To begin, let's start somewhere really easy: tally marks.

Tally Marks and Roman Numerals

If you're keeping track of a number which is slowly increasing, or you need to take a quick informal count of somthing like raised-hand votes, you might use tally marks. This primitive counting system involves simply marking a slash to count one instance of a particular thing. Two slashes means two instances, etc.:

   | = 1
  || = 2
 ||| = 3
|||| = 4
Enter fullscreen mode Exit fullscreen mode

...when we get to five, we sometimes group these slashes so they're easier to count. Instead of ||||||| for seven, we might write |||| ||. This makes the groups of marks easier to add up in large amounts, but we still need a single tally for each count, whether vertical or horizontal.

Tally marks are a unary (pronounced: you-nair-ee) numbering system, a word which comes from the Latin unus, meaning one, because there is only a single symbol -- the tally. Five tally marks means five, six-hundred and seventy tally marks means six-hundred and seventy. Obviously, tally marks can get pretty cumbersome pretty quickly.

A slightly easier numbering scheme is the Roman numeral system. This is the one that's used to number Superb Owls, Grand Theft Auto games, and Star Wars movies. In the Roman numeral system, the following figures on the left stand for the respective amounts on the right:

     I = 1
    II = 2
   III = 3
  IIII = 4
Enter fullscreen mode Exit fullscreen mode

...that's easy, right? They look a bit like tally marks, where each tally increases the value of the number by one.

At some point, a very intelligent ancient person realised that you could use alternate symbols to denote bigger quantities. Instead of five tally marks (IIIII, or IIII with a horizontal slash through them), we could use V as a stand-in. Instead of two Vs, we could use an X to denote ten tally marks. Our list of numbers can then be expanded:

  V     = 5
  VI    = 6
  VII   = 7
  VIII  = 8
  VIIII = 9
  X     = 10
Enter fullscreen mode Exit fullscreen mode

The ancients realised that IIII and VIIII could be confusing to read at a glance. The human brain can only hold about 3-5 symbols or ideas in working memory at once, so grouping that many symbols together visually is a challenge. This led to the development of subtractive notation in the Roman numeral system, where a smaller number immediately before a larger one means that the smaller number is subtracted from the larger one. So IIII instead becomes IV (5 - 1 = 4), VIIII becomes IX (10 - 1 = 9), and so on. The improved numbering system is then:

     I = 1    6  = VI
    II = 2    7  = VII
   III = 3    8  = VIII
    IV = 4    9  = IX
     V = 5    10 = X
Enter fullscreen mode Exit fullscreen mode

More symbols are introduced for fifty (L), one hundred (C), five hundred (D), and one thousand (M). As Roman numerals were used for everyday counting, numbers larger than a few thousand were uncommon and there is no single agreed-upon way that to write numbers like two- or five- or ten-thousand, and so on. An extended table of Roman numerals might look like:

  I    = 1    XI    = 11   ...           ...
  II   = 2    XII   = 12   XLII   = 42   XCII   = 92
  III  = 3    XIII  = 13   XLIII  = 43   XCIII  = 93
  IV   = 4    XIV   = 14   XLIV   = 44   XCIV   = 94
  V    = 5    XV    = 15   XLV    = 45   XCV    = 95
  VI   = 6    XVI   = 16   XLVI   = 46   XCVI   = 96
  VII  = 7    XVII  = 17   XLVII  = 47   XCVII  = 97
  VIII = 8    XVIII = 18   XLVIII = 48   XCVIII = 98
  IX   = 9    XIX   = 19   XLIX   = 49   XCIX   = 99
  X    = 10   XX    = 20   L      = 50   C      = 100
Enter fullscreen mode Exit fullscreen mode

Roman numerals are an alphabetical numbering system and each number always stands for a specified amount. So each X in XXX means 10 (for a total of 10 + 10 + 10 = 30). This is in contrast to our familiar decimal system where the "5" in "50" has a different meaning (five tens) than the "5" in "5000" (five thousands), but we'll come back to this in a bit.

Roman numerals are fine for most day-to-day uses. They're easy to add -- just group together all similar symbols and "compress" every five I symbols into one V, then "compress" every two Vs into one X, and so on:

XXXVII + LXIII (= 37 + 63)
= L + XXXX + V + IIIII
= L + XXXX + VV
= L + XXXXX
= L + L
= C (= 100)
Enter fullscreen mode Exit fullscreen mode

(This is a bit more difficult with symbols like IV and XC, which first need to be "expanded" to IIII and LXXXX, respectively.) They're easy to subtract, too -- just remove all symbols from the minuend (the number being subtracted from) which appear in the subtrahend (the number being subtracted):

XLI - XXVIII (= 41 - 28)
= XXXXI - XXVIII (first, "expand" `XL` to `XXXX`)
=   XX  -   VII
Enter fullscreen mode Exit fullscreen mode

...and expand any factors in the minuend as necessary

= XVIIIII - VII
= X   III
= XIII (= 13)
Enter fullscreen mode Exit fullscreen mode

Roman numerals quickly become overwhelming when dealing with large numbers, or trying work with fractional amounts (which are broken into twelfths rather than tenths like our familiar decimal system). All in all, useful, but not great. Surely there's a better way?

Positional Numbering and Arabic Numerals

There is no single, correct way to represent a number. Whichever system is most useful and easiest to work with will naturally become more popular than the others. In the Middle Ages, a positional numbering system was invented in India and spread west and east by Arabic scholars, eventually becoming popular in Europe, China, and Russia. This system, now known as the Hindu-Arabic numeral system (aka. the Arabic numeral system or just "Western" numerals), is the one you're probably the most familiar with.

In the Arabic numeral system, there are ten distinct figures which represent different quantities. This makes Arabic numerals a decimal numbering system, from the Latin decimus, meaning "one tenth". The ten figures of the Arabic numbering system -- with their English names -- are (compared to Roman numerals and tally marks):

  0 ("zero")  =      = 
  1 ("one")   = I    = |
  2 ("two")   = II   = ||
  3 ("three") = III  = |||
  4 ("four")  = IV   = ||||
  5 ("five")  = V    = |||||
  6 ("six")   = VI   = ||||| |
  7 ("seven") = VII  = ||||| ||
  8 ("eight") = VIII = ||||| |||
  9 ("nine")  = IX   = ||||| ||||
Enter fullscreen mode Exit fullscreen mode

The most critical one is 0, which represents a lack of any tally marks. It may not seem like it to you, but creating a symbol for "nothing" is a huge conceptual leap. If you're used to using numerals for counting, why would you ever need a symbol for "nothing"? Doesn't the lack of symbols represent "nothing"?

The Arabic numeral system uses this 0 figure in an extremely clever way, as well. In the Roman numeral system, if we want to write three ones (ie. the number three), we need to write the figure for one three times (III)? and if we want to write three tens (ie. the number thirty), we need to write the figure for ten three times (XXX)? The same applies to three hundred (CCC) and three thousand (MMM)! We need a new figure (I, X, C, M) for every power of ten. And when we run out of figures, we run out of numbers.

Instead of doing that, the Arabic system uses what's known as a positional numbering system, in that the position of a given figure within the expression for a number determines its value. so if we want three ones, we can use the figure 3 in a particular position in a number (the ones' position); if we want three hundreds, we use the same figure in a different position in the same number (the hundreds' position). We can use 0 as a placeholder to show what position the different figures are in.

Because Arabic numbers are a base-10 (decimal) system, as well as a positional system, each position in a given number is 10 times as large as the position before it. The rightmost position is the ones' position, and to the left of that is the tens' position, then the hundreds' position, and so on:

123
|||
|||____(3)__ ones = (3 * 10^0) = (3 *   1) =   3
||
||___(2)____ tens = (2 * 10^1) = (2 *  10) =  20
|
|__(1)__ hundreds = (1 * 10^2) = (1 * 100) = 100
Enter fullscreen mode Exit fullscreen mode

The number 123 translates to 1 (one) hundred plus 2 (two) tens plus 3 (three) ones. If we subtract three ones from this number, we need to use the placeholder 0 (zero):

123 - 3 = 120
Enter fullscreen mode Exit fullscreen mode

Otherwise, if we simply dropped the ones' place (if we had no symbol for "nothing"), we would have the number 12, which is quite different from 120. The Arabic numeral system ingeniously uses this positional system, plus the figure 0, to denote any "counting" number imaginable (the positive integers and zero), in a compact form, with only ten distinct figures.

Addition and Subtraction

Addition and subtraction is also greatly improved with positional numbering systems. Instead of grouping together like figures like we had to do with Roman numerals, we can simply subtract the numbers position-by-position:

  345
-  23
-----
  322
Enter fullscreen mode Exit fullscreen mode

We subtract the ones' place from the ones' place, the tens' place from the tens' place, and so on. If a figure is missing in a certain position, we assume it to be 0 (so 23 above becomes 023). But, similar to how we had to "compress" and "expand" numbers in the Roman numeral system, we also must do that with Arabic numerals:

  234  Here, 5 is larger than 4, so we need to "expand" one
-  35  of the tens into ten ones. To make this calculation easier,
-----  I'll put each "place" into its own "box":

  [ 2][ 3][ 4]  We "trade" one of the tens in the minuend for ten
- [ 0][ 3][ 5]  ones, turning 4 in the ones' place into 14
--------------

  [ 2][ 2][14]  Now, we can easily subtract 5 from 14 to get 9.
- [ 0][ 3][ 5]  But we have a similar problem with the tens' place.
--------------

  [ 1][12][14]  "Expand" the 2 in the hundreds' place into ten tens
- [ 0][ 3][ 5]  in the tens' place, turning 2 tens into 12 tens
--------------

  [ 1][12][14]  Finally, we can subtract the numbers in each
- [ 0][ 3][ 5]  position without any more "expansions".
--------------
  [ 1][ 9][ 9] => 234-35=199
Enter fullscreen mode Exit fullscreen mode

This process is likely so ingrained in you that you don't even realise you're doing it. To better explore what is happening in the example above, let's build an addition table for Arabic numerals.

Addition and Multiplication in Decimal

Below, the leftmost number in each row after the first is added to the number at the top of each column to get the number in each cell. The first row is easy -- 0 plus any figure equals that original figure (x tally marks plus 0 (no) tally marks equals x tally marks):

+ 0 1 2 3 4 5 6 7 8 9
0 0 1 2 3 4 5 6 7 8 9

A subtraction table would look exactly the same for this first row, because any number minus 0 is that same number. We don't get much insight from a subtraction table, so we'll forgo that for now. The second row is slightly more complex, adding 1 to a figure increments it to the next figure in the sequence:

+ 0 1 2 3 4 5 6 7 8 9
0 0 1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9 ?

Note: subtracting 1 from a number is called decrementing.

...so 1+2=3, 1+4=5 and so on. But what comes after 9? We have no special symbol for ten ones, like we did in the Roman system. Instead, we use 0 as a placeholder in the ones' place, and put a 1 (one) in the tens' place to create the number 10 -- the result of our decimal positional number system. One more than nine ones is ten ones or one ten:

+ 0 1 2 3 4 5 6 7 8 9
0 0 1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9 10

Adding 2 to a number increments it twice, advancing it by two figures:

+ 0 1 2 3 4 5 6 7 8 9
0 0 1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9 10
2 2 3 4 5 6 7 8 9 10 11

Let's fill in the rest of the addition table:

+ 0 1 2 3 4 5 6 7 8 9
0 0 1 2 3 4 5 6 7 8 9
1 1 2 3 4 5 6 7 8 9 10
2 2 3 4 5 6 7 8 9 10 11
3 3 4 5 6 7 8 9 10 11 12
4 4 5 6 7 8 9 10 11 12 13
5 5 6 7 8 9 10 11 12 13 14
6 6 7 8 9 10 11 12 13 14 15
7 7 8 9 10 11 12 13 14 15 16
8 8 9 10 11 12 13 14 15 16 17
9 9 10 11 12 13 14 15 16 17 18

You can see above that when we run out of symbols (ie. when we need a number greater than 9), we make use of our positional numbering system, incrementing the next position from 0 (which is implied) to 1, and reducing the current position from 9 back to 0. We go through the same incrementing over and over until we reach 19, where, when we try to increment again, the tens' place will be incremented from 1 to 2, but the ones' place will jump from 9 back to 0. So the number which follows 19 is 20.

You can think of these "positions" like gears. We have to ratchet the "ones' place gear" through nine notches, and on the tenth notch, the next-biggest gear is incremented (the tens' place gear) one notch, while the current gear (the ones' place gear) has made a full turn and is back to where it started -- at 0.

Similarly, we can build a multiplication table. The first two rows are easy as 0 times any number is 0, and 1 times any number is that same number:

* 0 1 2 3 4 5 6 7 8 9
0 0 0 0 0 0 0 0 0 0 0
1 0 1 2 3 4 5 6 7 8 9

Multiplying a number by 2 is the same as adding that number to zero twice (2 times). So 2 x 1 (or 2 * 1) is the same as 0 + 1 + 1, which we already know from our addition table is 2. Similarly, 2 * 2 = 0 + 2 + 2 = 4, and 2 * 3 = 0 + 3 + 3 = 6. This pattern continues down the diagonal of the addition table, giving us the second row of the multiplication table. To avoid over-explaining this whole thing (with which you're probably already very familiar), here's the multiplication table in full for an Arabic-numeral based decimal system:

* 0 1 2 3 4 5 6 7 8 9
0 0 0 0 0 0 0 0 0 0 0
1 0 1 2 3 4 5 6 7 8 9
2 0 2 4 6 8 10 12 14 16 18
3 0 3 6 9 12 15 18 21 24 27
4 0 4 8 12 16 20 24 28 32 36
5 0 5 10 15 20 25 30 35 40 45
6 0 6 12 18 24 30 36 42 48 54
7 0 7 14 21 28 35 42 49 56 63
8 0 8 16 24 32 40 48 56 64 72
9 0 9 18 27 36 45 54 63 72 81

Note that the numbers on the diagonal of this table are the squares of the non-negative integers (0^2 = 0 * 0 = 0, 1^2 = 1, 2^2 = 4, 3^3 = 9, ...). This surely is all second-nature to you, but if you understand how these numbers are constructed, then you'll have a much better foundation upon which to understand how binary and hexadecimal and other numbers are constructed.

Ace of Base(s)

We call decimal a base-10 numbering system because there are ten unique decimal digits which can be used to construct numbers in the system (0 through 9). But there is nothing special about using ten symbols. Ancient decimal numbering systems probably got their start from people counting on their fingers (of which, usually, one has ten). But this is not always the case.

Octal (base-8)

For istance, the Pamean peoples of Mesoamerica employ a base-8 (aka. octal) system. This could arise just as naturally as a base-10 system; for example, the indigenous Yuki people of what is now southern California originally employed a similar system, counting on the spaces between the fingers, rather than the fingers themselves.

How would a Pamean count to ten? Well, in an octal number system, one has unique names and symbols for all numbers from zero through seven:

0 = "zero"
1 = "one"
2 = "two"
3 = "three"
4 = "four"
5 = "five"
6 = "six"
7 = "seven"
Enter fullscreen mode Exit fullscreen mode

But, just like we don't have a special symbol for ten in decimal (it's actually a 1 and a 0 next to each other), there would be no special symbol for eight in an octal system. In fact, if this octal numbering system was also positional, like the Arabic numeral system, then adding 1 to 7 should increase the eights' place from 0 to 1 and decrease the ones' place back to 0, just like it did in decimal. To avoid confusion, from this point on, we'll specify the base in the name of a number, so 8 in decimal would instead be 8 base 10. The octal number list above can then be expanded:

0 = "zero base eight"   10 = "eight base eight"     20 = "sixteen base eight"
1 = "one base eight"    11 = "nine base eight"      21 = "seventeen base eight"
2 = "two base eight"    12 = "ten base eight"       22 = "eighteen base eight"
3 = "three base eight"  13 = "eleven base eight"    23 = "nineteen base eight"
4 = "four base eight"   14 = "twelve base eight"    24 = "twenty base eight"
5 = "five base eight"   15 = "thirteen base eight"  25 = "twenty-one base eight"
6 = "six base eight"    16 = "fourteen base eight"  26 = "twenty-two base eight"
7 = "seven base eight"  17 = "fifteen base eight"   27 = "twenty-three base eight"
Enter fullscreen mode Exit fullscreen mode

Notice that no single symbol exists anywhere for "eight", just like no single symbol exists for "ten" in a decimal system. Instead, when we reach 7 in the ones' place, if we wish to increment the number, we increment the eights' place instead, and decrease the ones' place to 0.

The representation of a number in octal, just like in decimal, has encoded in it a formula for how to calculate it. An analog to the above diagram we made for decimal could, in octal, be:

123 (base 8)
|||
|||____(3)_____ ones = (3 * 8^0) = (3 * 1)  =   3
||
||___(2)_____ eights = (2 * 8^1) = (2 * 8)  =  16
|
|__(1)__ sixty-fours = (1 * 8^2) = (1 * 64) =  64
Enter fullscreen mode Exit fullscreen mode

So 123 base eight does not equal 123 base ten, but rather 3 + 16 + 64 = 83 base ten. This makes sense because (all base ten below):

                                       __ "tens' place"
                                       |          __ "ones' place"
                                       |          |
[octal representation]                 |          |
(1 * 64) + (2 *  8) + (3 * 1)    =    (8 * 10) + (3 * 1)
 |          |          |              [decimal representation]
 |          |          |
 |          |          |__ "ones' place"
 |          |__ "eights' place"
 |__ "sixty-fours' place"
Enter fullscreen mode Exit fullscreen mode

An octal addition table would look like:

+ 0 1 2 3 4 5 6 7
0 0 1 2 3 4 5 6 7
1 1 2 3 4 5 6 7 10
2 2 3 4 5 6 7 10 11
3 3 4 5 6 7 10 11 12
4 4 5 6 7 10 11 12 13
5 5 6 7 10 11 12 13 14
6 6 7 10 11 12 13 14 15
7 7 10 11 12 13 14 15 16

Note that it looks exactly the same as a decimal addition table, until we get to values larger than 7. Then, we have to increment the eights' place and set the ones' place back to 0. An octal multiplication table would look like:

* 0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 0
1 0 1 2 3 4 5 6 7
2 0 2 4 6 10 12 14 16
3 0 3 6 11 14 17 22 25
4 0 4 10 14 20 24 30 34
5 0 5 12 17 24 31 36 43
6 0 6 14 22 30 36 44 52
7 0 7 16 25 34 43 52 61

It takes a bit of time to make, especially if you're not familiar with octal numbers, but you can easily check that it's accurate. Take any pair of numbers in the table and get the decimal equivalent of their product (for example 7 base ten times 6 base ten equals 42 base ten) and divide it by eight as many times as you can without fractions (so 42 base ten divided by 8 base ten equals 5 base ten with a remainder of 2 base ten). The result and the remainder will be the digit in the eights' place and the digit in the ones' place of the octal representation of that number (52 base eight == 42 base ten), respectively.

The 4 column has another cool property where every number either ends in 0 or 4. This is because when you add 4 to 0 in octal, you get 4, but when you add 4 to 4, you don't get 8 (because 8 doesn't exist in octal), instead you get 10. 4 in octal acts the same way as 5 in decimal, in that respect.

There's one more neat thing to notice on this addition table. In grade school you may have learned that when you multiply any number 2 through 9 by 9 (base-10) that the result has some interesting properties:

2 * 9 = 18  (all base-10)
3 * 9 = 27
4 * 9 = 36
5 * 9 = 45
6 * 9 = 54
7 * 9 = 63
8 * 9 = 72
9 * 9 = 81
Enter fullscreen mode Exit fullscreen mode

The first digit of the result (in the tens' place) is the first number, minus one. And the second digit of the result is the nine's complement of the first digit. In other words, it's the number that you need to add to the first digit of the product for the sum to be 9:

                                   __ [first digit]
                                  |      __ [second digit]
                                  |     |
x = 2:  2 * 9 = 18 => [x] * 9 = [x-1][9-(x-1)]
x = 3:  3 * 9 = 27 => [x] * 9 = [x-1][9-(x-1)]
x = 4:  4 * 9 = 36 => [x] * 9 = [x-1][9-(x-1)]
x = 5:  5 * 9 = 45 => [x] * 9 = [x-1][9-(x-1)]
x = 6:  6 * 9 = 54 => [x] * 9 = [x-1][9-(x-1)]
x = 7:  7 * 9 = 63 => [x] * 9 = [x-1][9-(x-1)]
x = 8:  8 * 9 = 72 => [x] * 9 = [x-1][9-(x-1)]
x = 9:  9 * 9 = 81 => [x] * 9 = [x-1][9-(x-1)]
Enter fullscreen mode Exit fullscreen mode

The same thing happens in octal, but instead of a nine's complement, we have a seven's complement:

x = 2:  2 * 7 = 16 => [x] * 7 = [x-1][7-(x-1)]
x = 3:  3 * 7 = 25 => [x] * 7 = [x-1][7-(x-1)]
x = 4:  4 * 7 = 34 => [x] * 7 = [x-1][7-(x-1)]
x = 5:  5 * 7 = 43 => [x] * 7 = [x-1][7-(x-1)]
x = 6:  6 * 7 = 52 => [x] * 7 = [x-1][7-(x-1)]
x = 7:  7 * 7 = 61 => [x] * 7 = [x-1][7-(x-1)]
Enter fullscreen mode Exit fullscreen mode

...pretty cool, huh?

Quaternary (base-4)

Number systems, as mentioned before, can be in any base. They can be base 20 or 60 or 6.02 * 10^24. Larger bases require more unique symbols but can represent large numbers more compactly. (If you had a base-10,000 system, then you could represent any number from 0 to 9,999 with just a single unique character.) Smaller bases require fewer symbols but even relatively small numbers require us to use those symbols several times, making the numbers "big", in terms of area, when printed on a page.

If you're familiar with how memory is stored within a computer, you may be familiar with the terms bit and byte. A bit is a single binary digit of information, a 0 or a 1 (will come back to this in a second). A byte is composed of eight consecutive bits (also known as an octet of bits). But the definition of a byte hasn't always unambiguously been 8 bits. In the mid-to-late 1950s, the size of a "byte" depended on who you were talking to, and it could be 8, 6, or even 4 bits. Nowadays, four consecutive bits (a "quartet") is also sometimes called a nybble.

The powers of 2 are very important in computer science, for reasons we will cover when we get to binary. So instead of looking at base-7 or base-5 numbers, let's jump from base-8 down to base-4. To see how our method for developing addition and multiplication tables scales down, let's try to construct a quaternary addition table. To start, we won't need any digit larger than 3 (because four will be represented by 10):

+ 0 1 2 3
0
1
2
3

We can fill in the trivial row and column, where we add 0:

+ 0 1 2 3
0 0 1 2 3
1 1
2 2
3 3

Then, let's fill in any sum which is less than four:

+ 0 1 2 3
0 0 1 2 3
1 1 2 3
2 2 3
3 3

Now, where the sum is four, we write 10, because in quaternary, we have a fours' place and a ones' place, so adding 1 to 3 will cause the fours' place to increment, and the ones' place to "roll over" back to 0:

+ 0 1 2 3
0 0 1 2 3
1 1 2 3 10
2 2 3 10
3 3 10

If we add 1 to 10 in quaternary, we get 11; and if we add 1 to 11, we get 12:

+ 0 1 2 3
0 0 1 2 3
1 1 2 3 10
2 2 3 10 11
3 3 10 11 12

Great! Our table is a lot easier to fill in and work with, and a lot smaller now. Let's check that it makes sense. In the last row, we have 3 base 4 + 2 base 4 = 11 base 4. Does that check out? Well, 11 base 4 has a 1 in the fours' place and a 1 in the ones' place, so it's equivalent to 5 base ten, which is 3 base ten + 2 base ten! It seems okay! A multiplication table in quaternary looks like:

* 0 1 2 3
0 0 0 0 0
1 0 1 2 3
2 0 2 10 12
3 0 3 12 21

Again, since 2 is half of the base, we get that alternating 0-2-0-2 in the ones' place going down the 2 column, similar to what we had with the 4 column in octal. And we also have a three's complement in the 3 column, though there are only two examples:

x = 2:  2 * 3 = 12 => [x] * 3 = [x-1][3-(x-1)]
x = 3:  3 * 3 = 21 => [x] * 3 = [x-1][3-(x-1)]
Enter fullscreen mode Exit fullscreen mode

So there are fewer symbols and the addition and multiplication tables are easier to work out. But one of the downsides of smaller bases is that numbers require more ink to print, because they require larger numbers of characters. For instance, the number 123 base four equals only 27 base ten:

123 (base 4)
|||
|||____(3)__ ones = (3 * 4^0) = (3 * 1)  =   3
||
||___(2)___ fours = (2 * 4^1) = (2 * 4)  =   8
|
|__(1)__ sixteens = (1 * 4^2) = (1 * 16) =  16
Enter fullscreen mode Exit fullscreen mode

...and 3 + 8 + 16 = 27 base ten. The effect is not as noticable for small numbers but it becomes significant for very large numbers. For example, the world population as of this writing is estimated to be about 7.55 billion people. In decimal and in quaternary, those numbers are:

7550000000        (decimal)
13002000331232000 (quaternary) -- 70% more digits
Enter fullscreen mode Exit fullscreen mode

When writing a number n in base b, if n is large relative to b, the number of digits required to write n in that base is inversely proportional to ln(b). Since b is 4 for quaternary, a large number should require ln(10)/ln(4) ~ 1.66x as many digits in quaternary as in decimal. (source)

Binary (base-2)

The smallest useful positional numbering system is base 2 or binary. If we tried to use a unary system, as we did with tally marks, then every "place" becomes a ones' place. A base-1 numbering system means that every place only counts for 1:

111 (base 1) = 3 (base 10)
|||
|||____(1)__ ones = (1 * 1^0) = (1 * 1) = 1
||
||___(1)____ ones = (1 * 1^1) = (1 * 1) = 1
|
|__(1)______ ones = (1 * 1^2) = (1 * 1) = 1
Enter fullscreen mode Exit fullscreen mode

What's worse is that, with a unary system, we don't have a second symbol (by definition), so we have no 0 character as a placeholder. This makes the unary system...kind of useless, right? It's just tally marks. Instead, if we add that second symbol in (0) to create a binary system, we can do a bit more. In fact, we can do everything that a computer can do.

In a binary system, we have two symbols: 0 and 1. When we add 1 to 0 (or vice versa), we get 1. When we add 1 to 1, we've already run out of symbols! So, like we've done previously, we increment the next place (which, in this case, is the twos' place) and reduce the ones' place to 0:

1 + 1 = 10 (binary)
Enter fullscreen mode Exit fullscreen mode

Hence the old joke: there are 10 kinds of people in this world. Those who understand binary, and those who don't.

A binary addition table is extremely simple:

+ 0 1
0 0 1
1 1 10

A binary multplication table is even easier, as all the regular rules for multiplying by 1 and 0 apply:

* 0 1
0 0 0
1 0 1

Instead of having a ones' place, a tens' place, a hundreds' place, and so on, a binary number has a one's place, a twos' place, a fours' place and so on, through all the powers of two:

10101 (base 2) = 21 (base 10)
|||||
|||||______(1)__ ones = (1 * 2^0) = (1 *  1) =  1
||||
||||_____(0)____ twos = (0 * 2^1) = (0 *  2) =  0
|||
|||____(1)_____ fours = (1 * 2^2) = (1 *  4) =  4
||
||___(0)______ eights = (0 * 2^3) = (0 *  8) =  0
|
|__(1)______ sixteens = (1 * 2^4) = (1 * 16) = 16
Enter fullscreen mode Exit fullscreen mode

Note that the binary representation is much less compact than the decimal representation, but that it only requires those two digits -- 0 and 1. Binary digits have become so commonplace in the study of computers that a word was coined to shorten the phrase: bit. A bit is a single binary digit -- either a 0 or a 1.

Binary is the "language" of computers. Anything that can be represented in one of two states can be represented by a bit. A switch can be open or closed, on or off; electricity can be flowing through a circuit or not; a magnet can have its positive end pointing up or down. The universe is full of black-and-white situations which can be represented by a single bit.

Computers were originally built of huge messes of circuits and huge chunks of magnetic materials and vaccum tubes. In those early days, binary code was (and still is) the most natural way to represent information in a complex circuit -- electricity either is or is not flowing through this particular wire or transistor or whatever. And although several extremely-successful decimal computers were built throughout the 1950s and 60s, shrinking hardware and a host of other factors led to binary beating out decimal in the middle of the past century. To understand computers, you must understand binary.

Hexadecimal (base-16)

So far, we've focused on removing symbols from the Arabic numeral system, creating base-8, base-4 and even base-2 number systems. But what if we try adding symbols? What if, instead of base-10, we developed a base-16 numeral system? A base-16 system is a hexadecimal system (from "hexa", six; and "deca", ten).

This time, instead of removing some of our familiar Arabic numerals, we need to add new ones. We need single-character representations of all numbers zero through fifteen:

  0 ("zero")     =      = 
  1 ("one")      = I    = |
  2 ("two")      = II   = ||
  3 ("three")    = III  = |||
  4 ("four")     = IV   = ||||
  5 ("five")     = V    = |||||
  6 ("six")      = VI   = ||||| |
  7 ("seven")    = VII  = ||||| ||
  8 ("eight")    = VIII = ||||| |||
  9 ("nine")     = IX   = ||||| ||||
  ? ("ten")      = X    = ||||| |||||
  ? ("eleven")   = XI   = ||||| ||||| |
  ? ("twelve")   = XII  = ||||| ||||| ||
  ? ("thirteen") = XIII = ||||| ||||| |||
  ? ("fourteen") = XIV  = ||||| ||||| ||||
  ? ("fifteen")  = XV   = ||||| ||||| |||||
Enter fullscreen mode Exit fullscreen mode

So we need six new symbols. Usually, we borrow an idea from the Romans and use alphabetic characters to make up these six remaining symbols, but they really could be anything. You could use emoji or arrows or wingdings or whatever makes you happy. For ease of use, though, let's use the first six letters of the alphabet:

  0 ("zero")     =      = 
  1 ("one")      = I    = |
  2 ("two")      = II   = ||
  3 ("three")    = III  = |||
  4 ("four")     = IV   = ||||
  5 ("five")     = V    = |||||
  6 ("six")      = VI   = ||||| |
  7 ("seven")    = VII  = ||||| ||
  8 ("eight")    = VIII = ||||| |||
  9 ("nine")     = IX   = ||||| ||||
  A ("ten")      = X    = ||||| |||||
  B ("eleven")   = XI   = ||||| ||||| |
  C ("twelve")   = XII  = ||||| ||||| ||
  D ("thirteen") = XIII = ||||| ||||| |||
  E ("fourteen") = XIV  = ||||| ||||| ||||
  F ("fifteen")  = XV   = ||||| ||||| |||||
Enter fullscreen mode Exit fullscreen mode

In this encoding, B means "eleven". This is just as valid as 11 meaning "eleven" in a base-10 system. There is nothing inherently "good" or "bad" about either system. We use different bases when they suit us. Let's look at the hexadecimal addition table, filling in everything up to F first:

+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0 1 2 3 4 5 6 7 8 9 A B C D E F
1 1 2 3 4 5 6 7 8 9 A B C D E F
2 2 3 4 5 6 7 8 9 A B C D E F
3 3 4 5 6 7 8 9 A B C D E F
4 4 5 6 7 8 9 A B C D E F
5 5 6 7 8 9 A B C D E F
6 6 7 8 9 A B C D E F
7 7 8 9 A B C D E F
8 8 9 A B C D E F
9 9 A B C D E F
A A B C D E F
B B C D E F
C C D E F
D D E F
E E F
F F

...what comes after F? Well, we do the same thing we did in all the previous bases: increment the next place (in this case, the sixteens' place) and reduce the current place back to 0. So F + 1 = 10 in hexadecimal:

+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0 1 2 3 4 5 6 7 8 9 A B C D E F
1 1 2 3 4 5 6 7 8 9 A B C D E F 10
2 2 3 4 5 6 7 8 9 A B C D E F 10
3 3 4 5 6 7 8 9 A B C D E F 10
4 4 5 6 7 8 9 A B C D E F 10
5 5 6 7 8 9 A B C D E F 10
6 6 7 8 9 A B C D E F 10
7 7 8 9 A B C D E F 10
8 8 9 A B C D E F 10
9 9 A B C D E F 10
A A B C D E F 10
B B C D E F 10
C C D E F 10
D D E F 10
E E F 10
F F 10

We can continue adding 1 to numbers greater than F and we don't hit another "rollover" until 1F, which doesn't happen in the addition table:

+ 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0 1 2 3 4 5 6 7 8 9 A B C D E F
1 1 2 3 4 5 6 7 8 9 A B C D E F 10
2 2 3 4 5 6 7 8 9 A B C D E F 10 11
3 3 4 5 6 7 8 9 A B C D E F 10 11 12
4 4 5 6 7 8 9 A B C D E F 10 11 12 13
5 5 6 7 8 9 A B C D E F 10 11 12 13 14
6 6 7 8 9 A B C D E F 10 11 12 13 14 15
7 7 8 9 A B C D E F 10 11 12 13 14 15 16
8 8 9 A B C D E F 10 11 12 13 14 15 16 17
9 9 A B C D E F 10 11 12 13 14 15 16 17 18
A A B C D E F 10 11 12 13 14 15 16 17 18 19
B B C D E F 10 11 12 13 14 15 16 17 18 19 1A
C C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B
D D E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C
E E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D
F F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E

The multiplication table is a different story, though. It may look like a crazy jumble of letters and numbers, but I repeat, it's just as valid as any other numbering system. It is just unfamiliar to the layperson:

* 0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 2 3 4 5 6 7 8 9 A B C D E F
2 0 2 4 6 8 A C E 10 12 14 16 18 1A 1C 1E
3 0 3 6 9 C F 12 15 18 1B 1E 21 24 27 2A 2D
4 0 4 8 C 10 14 18 1C 20 24 28 2C 30 34 38 3C
5 0 5 A F 14 19 1E 23 28 2D 32 37 3C 41 46 4B
6 0 6 C 12 18 1E 24 2A 30 36 3C 42 48 4E 54 5A
7 0 7 E 15 1C 23 2A 31 38 3F 46 4D 54 5B 62 69
8 0 8 10 18 20 28 30 38 40 48 50 58 60 68 70 78
9 0 9 12 1B 24 2D 36 3F 48 51 5A 63 6C 75 7E 87
A 0 A 14 1E 28 32 3C 46 50 5A 64 6E 78 82 8C 96
B 0 B 16 21 2C 37 42 4D 58 63 6E 79 84 8F 9A A5
C 0 C 18 24 30 3C 48 54 60 6C 78 84 90 9C A8 B4
D 0 D 1A 27 34 41 4E 5B 68 75 82 8F 9C A9 B6 C3
E 0 E 1C 2A 38 46 54 62 70 7E 8C 9A A8 B6 C4 D2
F 0 F 1E 2D 3C 4B 5A 69 78 87 96 A5 B4 C3 D2 E1

You can tell by looking at the 2 column in the above table that A, C, and E are even numbers in hexadecimal. You can also see, in the 8 column, the familiar 8-0-8-0 behaviour that we noticed with 4 in octal. The F column also shows (after the 1 row) two-digit numbers where the digits are F's complement to each other ((1 + E) = (2 + D) = ... = F), as we saw before with decimal and octal. Finally, the numbers on the diagonal of the table above are again the squares of those numbers, so 2^2 = 4, 3^2 = 9, and 4^2 = 10, which, remember, means 1 sixteen and 0 ones in hexadecimal.

There's a real beauty with these bases. The same regular patterns occur whether you work in base-10 or base-8 or base-16. Any of these systems could be useful in particular situations, and in fact they are used by computer scientists very regularly. After base-10, the two most common bases in computer science are probably base-2 (binary) and base-16 (hexadecimal).

Working With Multiple Bases

Base-N to Decimal

Sometimes, we need to work with numbers in different bases, and we need to be able to convert back and forth from one base to another. It's pretty easy, usually, to convert from a non-decimal base into a decimal base. You just need to remember how each position is defined in a given base:

base-N

[4][1][2][3]
 |  |  |  |
 |  |  |  |__ ones' place (always)
 |  |  |_________ N place
 |  |________ (N^2) place
 |___________ (N^3) place, etc.
Enter fullscreen mode Exit fullscreen mode

So to convert from base-N to decimal, you multiply the rightmost digit by 1; and add that to the next digit, which you multiply by N; and add that to the next digit, which you multiply by N^2; ...

base-N to decimal

[4][1][2][3] => 3 + (2 * N) + (1 * N^2) + (4 * N^3) = ...
Enter fullscreen mode Exit fullscreen mode

Obviously, if there's a 4 in the number, then the base must be at least N = 5 (because, remember, there's no symbol for N in a base-N system, like there's no symbol for "ten" in a base-10 system). Suppose the base above is N = 5, then:

base-5 to decimal

[4][1][2][3] => 3 + (2 * 5) + (1 * 5^2) + (4 * 5^3) = 3 + 10 + 25 + 500 = 538 base ten
Enter fullscreen mode Exit fullscreen mode

Decimal to Binary

Converting from decimal into a particular base is a bit trickier. Suppose we have the number 637 base ten, and we want to convert it to binary (base-2). We must find the largest power of 2 which we can subtract from the original number 637 base 10 where the result of the subtraction is not a negative number. Let's make a short table of powers of 2:

 2^0 =    1
 2^1 =    2
 2^2 =    4
 2^3 =    8
 2^4 =   16
 2^5 =   32
 2^6 =   64
 2^7 =  128
 2^8 =  256
 2^9 =  512
2^10 = 1024
Enter fullscreen mode Exit fullscreen mode

It looks like 2^9 is the largest power of 2 base ten that we can subtract from 637 base ten without the result being negative:

637 - 2^9 = 637 - 512 = 125
Enter fullscreen mode Exit fullscreen mode

Great! Remember 2^9 and let's keep going. We now have 125 base 10. The largest power of 2 we can subtract from this is 2^6:

125 - 2^6 = 125 - 64 = 61
Enter fullscreen mode Exit fullscreen mode

Okay! Write down 2^6 as well, and let's continue for a few more terms:

61 - 2^5 = 61 - 32 = 29  (remember 2^5)
29 - 2^4 = 29 - 16 = 13  (remember 2^4)
13 - 2^3 = 13 -  8 =  5  (remember 2^3)
 5 - 2^2 =  5 -  4 =  1  (remember 2^2)
 1 - 2^0 =  1 -  1 =  0  (...and   2^0)
Enter fullscreen mode Exit fullscreen mode

Fantastic! The powers we used here give us the positions of the base-2 number which are 1, rather than 0. In this case, the first position is numbered 0 (for 2^0 = 1 for the ones' place) and the second is numbered 1 (for 2^1 = 2 for the twos' place). So we have 1 at the 0th, 2nd, 3rd, 4th, 5th, 6th, and 9th places, and 0 everywhere else:

[1][0][0][1][1][1][1][1][0][1] = 1001111101 = 637 base ten
Enter fullscreen mode Exit fullscreen mode

Decimal to Octal

For binary, we can only ever have a 1 or a 0 at a given position. We can either subtract a particular power of 2 from a number or we can't (without going negative). It's a bit more difficult with larger bases. Let's try octal, for instance.

Let's convert 637 base ten to octal. We start by writing the powers of eight (in base-10):

8^0 =    1
8^1 =    8
8^2 =   64
8^3 =  512
8^4 = 4096
Enter fullscreen mode Exit fullscreen mode

Then, we subtract the largest one we can from 637 base ten -- 512 base ten (8^3):

637 - 512 = 125
Enter fullscreen mode Exit fullscreen mode

From 125 base ten we can subtract 64 base ten (8^2) just once:

125 - 64 = 61
Enter fullscreen mode Exit fullscreen mode

At this point, we have a different situation than the one we encountered with binary. Here, we can subtract a power of eight multiple times. In fact, we can subtract 8^1 (or just 8) exactly seven times:

61 - (8^1)*7 = 61 - 56 = 5
Enter fullscreen mode Exit fullscreen mode

Then, we must subtract 1 (8^0) five times:

5 - (8^0)*5 = 5 - 5 = 0
Enter fullscreen mode Exit fullscreen mode

So in total, we found that 637 base ten is equal to...

637 = (1 * 8^3) + (1 * 8^2) + (7 * 8^1) + (5 * 8^0)
Enter fullscreen mode Exit fullscreen mode

Double-check that the right side of the above equation equals the left-side in base ten. To convert from decimal to octal, we take the multiplicative factors of the powers of eight, above (1, 1, 7, 5) and arrange them so that the 0th power is the rightmost digit, then the 1st power is to the left of that, and so on:

637 base ten = 1175 base 8
Enter fullscreen mode Exit fullscreen mode

Hexadecimal to Decimal

For web designers, probably the most common non-decimal number system you'll regularly encounter is the hexadecimal system. Hexadecimal codes -- or just "hex" codes, for short -- are used to describe colors used on the web. For instance, #3CB371 is a blue-ish, green-ish, mossy color; #708090 is a very slightly blue-ish grey; #D2691E is an orange/brown color; and so on.

But how do these numbers correspond to the colors they represent?

Well, a hex code is actually three hexadecimal numbers in one:

#3CB371 => [3C] [B3] [71]
#708090 => [70] [80] [90]
#D2691E => [D2] [69] [1E]
Enter fullscreen mode Exit fullscreen mode

A two-digit hexadecimal number has a range from 00 to FF, where 00 is equivalent to 0 base ten and FF means F sixteens and F ones. F base 16 is the same as 15 base 10, so:

hexadecimal: FF base 16 = ( F * 16) + ( F * 1) = 
    decimal:              (15 * 16) + (15 * 1) = 255
Enter fullscreen mode Exit fullscreen mode

So a two-digit hexadecimal number can encode any number between 0 base ten and 255 base ten, inclusive. The six digit hex codes above give three numbers on the range 0-255 base ten. Those numbers give the relative "amount" of red, green, and blue in a color:

                                red      green     blue         red     green     blue
#3CB371 => [3C] [B3] [71] => [ 60/255] [179/255] [113/255] => [23.53%] [70.20%] [44.31%]
#708090 => [70] [80] [90] => [112/255] [128/255] [144/255] => [43.92%] [50.20%] [56.47%]
#D2691E => [D2] [69] [1E] => [210/255] [105/255] [ 30/255] => [82.35%] [41.18%] [11.76%]
Enter fullscreen mode Exit fullscreen mode

The pixels on your screen are actually three "pixels" in one -- there's a small bit of red, a small bit of green, and a small bit of blue. The numbers in a hex code tell each chunk of the pixel how brightly it should shine. It all three are shining at the same strength, the color will appear to be white, black, or a shade of grey in between. When the three colors shine at different relative strengths, we can get almost any color imaginable.

Summary

I hope this was a useful introduction to different bases, how we work with numbers in those bases, and how useful binary and hexadecimal numbers are. Though they may seem scary at first, working with binary and hexadecimal numbers can be as easy as working with decimals -- you just have to practice!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .