9 * 10^0 = 9 * 1 = 9
+
3 * 10^1 = 3 * 10 = 30
+
1 * 10^2 = 1 * 100 = 100
=
100 + 30 + 9
The reason that this setup can allow for each integer to be represented is that each digit to the left of the core digit (I made that term up but I'm sure you can intuit that I'm referring to the 9 in the above example) can be expressed as just a heap of 'ones'. Consider the number 39:
There's certainly nothing special about using a base of 10 as opposed to another other base. All the above rules apply to something like base 8 (octal numbers). Base 8 uses the symbols 0,1,2,3,4,5,6 and 7. If you're working with base 8 and you see the number 139 it means 9 * 8^0 + 3 * 8^1 + 1 * 8^2 = (in base 10) 9 + 24 + 64 = 97. Just like with base 10, base 8 can represent every integer and each integer has a unique representation which is pretty cool in my opinion. We could have evolved to have 8 fingers rather than 10 and all the little tricks we learn from multiplication like 'when timesing by 5 just halve the number and add a 0 digit' would be gone and we'd have other tricks to easily work our way around arithmetic using the base.
You may wonder 'why would we ever need to worry about bases other than 10 if it's obvious that we can do everything we need to using the base'? The answer is that computers can only represent data in the form of bits (1s and 0s) meaning that if you want to store the number 139 (in base 10) in a computer you'll need to find a way to convert it into 1's and 0's. Well how about a base which has two symbols, 0 and 1, called 'binary'. A lot of this post will be about converting between the two.
First off let's visualise what's going on with different bases of numbers. The following picture shows what is looks like when a heap of bases represent the number that base 10 calls 39. On the left of each level is the size of that level's 'chunks' i.e. what one chunk represents in terms of ones. The numbers on the right hand side of each level are the symbols that represent the number of chunks in the level. Reading top-down through the symbols, you get the number that the base spits out when you ask it what its representation of 39 is. For base infinity i.e. the base with only a single digit, I just made some crazy looking symbol.
As you can see, the bottom level of base 4 is never going to have 4 chunks because that can be represented by a single chunk in the level above it, meaning it can only ever have a max of 3 chunks. The same can be said for the second (purple) level; it can only have a max of 3 chunks because if it has 4, that can be represented by a single chunk in the green level. Not only 'can' it be represented like that, but it must be, as base 4 only had 4 symbols: 0, 1, 2 and 3, meaning you cannot have 4 chunks on one level and if you do, you just give it to the level above.
let's look at how you could construct these numbers if you just started with the row of ones from base infinity. We'll do base 3 first: you know you can't represent all 39 of the chunks on the bottom row because your max symbol is 2 so you tell all of the chunks on the bottom row to find two friends and form a chunk on the above row. Luckily for us there aren't any leftovers so we can just go to the next row and see if we've got few enough chunks this time. So we look at the purple row and we have 13 chunks which is still too much meaning we ask the chunks to form groups of 3 and jump to the next row. In this case we have 1 leftover (remainder) who didn't have the social skills to get in early with the grouping, so he gets left on that level (no problem for us though because there's 1 chunk and we can use the symbol '1' to represent that). So we had 13 chunks and divided it into 3 groups and ended up with 1 remainder, so we've got 4 groups on the green level. Again, it's too much so we get them to form groups of 3 again, and again 1 gets left behind. We go up to the cyan level and because we have a number of chunks (1) that we have a symbol for, we can stop there (in fact we wouldn't be able to go up another level if we wanted to because 1 chunk on the above level represents 3^4 = 81 ones which we clearly don't have on the cyan level).
So we end up with the number 1110 in base 3, with each '1' representing the chunks that didn't go on to the next level. We know each chunk contains 3 times as many ones as a chunk in the next level down and that in the bottom level 1 chunk = 1 one so going right to left we can count in base 10 to see we have
0 * 1 + 1*3 + 1*9 + 1*27 = 3 + 9 + 27 = 39
which is what we started with, meaning our method works.
So what exactly was our method? We start off with no digits and a string of ones (which we talk about as 39 in base 10 terms because we're so familiar with it) and we add a digit which is the remainder when we divide the 39 by our base (in this case 3), and then we actually divide the 39 by 3 and continue the process with the quotient (the non-remainder part of the division), until the result of dividing is 0 (because that's the point when there are no more chunks which can group up and go to the next level).
If we were to make an algorithm for this, starting with the number n and using base b it would look like this:
Convert_From_Base_Ten(n,b):
number <-- ""
while n != 0:
number <-- str(n % b) + number
n <-- n/b
return number
Above we did the reverse process where you take each digit and multiply it by its associated value, and then sum them all up e.g. 0 * 1 + 1*3 + 1*9 + 1*27 = 3 + 9 + 27 = 39. Let's see how the algorithm would look for that:
Convert_To_Base_Ten(number,b):
n <-- 0
for i in xrange(0,len(number)):
n <-- n + number[i] * b^(len(number)-1-i)
return n
So we've taken care of integer conversions, BUT unfortunately we've left out one whole half of the digits: the ones one right of the decimal point. Just as the core digit represents your base to the power of 0's and digits to the left have ascending powers, digits to the right i.e. the fractional digits have descending powers. the number 0.325 in base 10 is 3*10^-1 + 3*10^-2 + 3*10^-3. Likewise the number 0.101 in base 2 is 1*2^-1 + 0*2^-2 + 1*2^-3 = (in base 10) 0.5 + 0.125 = 0.625. This calculation should be evidence enough that going from any base to base 10 isn't difficult, but the same cannot be said for the reverse process.
To understand the process of going from base 10 to some other base, we must first realise that the digital number system lets us shift digits left or right by multiplying or dividing by the base. For example 104 in base ten becomes 1040 if you multiply by ten or 10.4 if you divide. If you consider the visualisation above, when you multiply a number by its base you're going to each level and giving each chunk (base - 1) friends which is precisely enough to jump up to the next level so everything gets shifted up one level, and the reverse is true for division.
I'll focus on binary because that's really the one big base that we care about: If you want to know what the fractional digits of a binary number are but you can only see the core digit, you can just multiply the number by 2 and write down the digit that shows up in the core digit, then repeat that process until you're left with just 0's coming in or some repeating sequence of digits. BEAR WITH ME. Alright so say you wanted find out the binary equivalent of the number 0.582 in decimal. You can follow the exact same process: just times by 2 and you'll get 1.164. The thing here is that the core digit doesn't lie i.e. it means the same thing regardless of what base you're talking about, meaning that 1 would have showed up if you started with 0.582 in binary, so you just follow the process and write it down. Now I would say times 1.164 by two but the problem here is that the first '1' isn't going anywhere; in binary it would be shifted to the right and gotten out of our way by another multiplication by 2. Considering that's not going to happen here we'll just need to manually get it out of our way by subtracting it off. So once we're left with 0.164 we can multiply by 2 again, giving us 0.328. We write down the digit (0) which means we now have '10' written down corresponding to 0.10 in binary. repeat the process; now we have 0.656, so write down another 0. After a while I ended up with 0.100101010111 which didn't look like it planned on terminating any time soon.
So that's our process for going from decimal to binary. Note that this works with all non-decimal numbers; however if you want to use something like hexadecimal (base 16) then you'll need to consider the two digits to the left of the decimal point rather than just the 1st (because if you times say 0.9 by sixteen the number you're left with overflows the core digit). Remember what I said above: the core digit is the only one which doesn't lie, so if you're dealing with hexadecimal you'll need to do a conversion from the decimal to hexadecimal i.e. a conversion within a conversion. Luckily this isn't difficult because you can just look at the first two digits and go straight to the hexadecimal equivalent i.e. 01 --> 1, 02 --> ... 10 --> A, 11 --> B ... 15 --> F. I would give an example but you would be surprised how hard it is to find a fractional number in base 10 which doesn't give a recurring sequences in base 16.
The general algorithm for converting a fractional number n from base 10 to base b is:
Convert_Fractional(n,b):
while n!=0 and (there aren't any repeating digit patterns):
i <-- -1
n <-- n * b
number <-- number + Convert_To_Base_Ten(<digits to the left of the decimal point>,b)*(b^i)
i <-- i - 1
n <-- n - <digits to the left of the decimal point>
Anyway so we know how to convert to and from base 10 in terms of both integer and fractional parts. But in truth we can convert between numbers of any two bases because our algorithms just happened to be thinking in terms of base 10 when you could be thinking in terms of any base.
Now going straight from binary to decimal and back is pretty laborious because it takes a lot of steps, but going from hexadecimal to decimal and back is very easy (far fewer digits) meaning it would be very convenient if converting between binary and hexadecimal was easy because it would allow us to use hexadecimal as a middleman between the two bases. Let's see if it is:
four binary digits can cover the same range of numbers as a single hexadecimal digit:
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
meaning the number 1101 1010 0101 . 0100 0011 1111 is the same as DA5.43F. We could have used the conventional techniques to work that out but obviously the ability to group the digits up into sequences of 4 and convert each one makes our job a lot easier. The story is similar with binary and base 8; except there are three binary digits to each octal digit. It works because the bases are all the same number to a different power. With hexadecimal, each digit (2^4 symbols) encapsulates the information of 4 binary digits (4 digits of 2^1 symbols each giving 2^4 total permutations).
So we know that going from converting between binary and hexadecimal is trivial, and we know converting between hexadecimal and decimal is easier than converting between binary and decimal, so it's clear that converting between binary and decimal is best done with the hexadecimal middle-man.
So far example we can use the number from above: 1101 1010 0101 . 0100 0011 1111 as an example. We get to the hexadecimal number DA5.43F and then from there we want to be decimal so we first look at the DA5 part:
5 * 16^0 + 10 * 16^1 + 13 * 16^2 = 160 + 832 = 3493
Looking at the 43F part :
4 * 16^-1 + 3 * 16^-2 + 15 * 16^-3 = 1/4 + 3/64 +15/1024 = 0.26538085937 in decimal.
So our final answer is 3493.26538085937. Very cool. Going back the other way we can start with 3493.26538 and try to get to hexadecimal:
3493 % 16 = 5
3493 / 16 = 218
218 % 16 = 10 = A
218 / 16 = 13
13 % 16 = 13 = D
13 / 16 = 0
so we get DA5 (good thing too because it's what we started with)
now for the fractional part:
0.26538 * 16 = [4].24608
0.24608 * 16 = [3].93728
0.93728 * 16 = [14].99648 = [E].99658
We can stop there if we think we're accurate enough (the fact that we truncated the value earlier means any further digit calculations wouldn't be very representative anyway). We ended up with 43E which isn't too far off the 43F that we started with; obviously the cause of this was also the truncation above. But nonetheless we're left with DA5.43E. Next step is getting this into binary: which digit for digit is just:
1101 1010 0101 . 0100 0011 1110
Not too bad! Well making this post has done wonders for my intuition on the subject and hopefully you can say the same. Next up I'll hopefully get around to posting about ways to speed up Prim's and Kruskal's Algorithms.