How To Write a Number Systems Calculator in Python | by Martin Andersson Aaberge | Towards Data Science

In this article, we will build a calculator we can use to convert any number from any base to any other base. In number systems, “Base” tells us how many numbers you have available. Binary has 2, octal has 8, decimal has 10 etc.
The program we are about to write can handle any number and base we throw at it. When we figure out the math behind it, it is very easy to set up.
This calculator is based on math (Aren’t they all?). We might need to refresh our memory on the theory of converting between bases (or learn something new.)
If you had science-driven math in school or studied computers at any level, chances are you have converted from one number system to another. You might remember the binary table or talks about “the decimal system”, “the octal system”, and “the hexadecimal system”.
The decimal system (Base-10) is the one we use daily when we count. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. When we run out of numbers, we add another position to the left → 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21... etc. When we run out of numbers again, we add a new position → 100, 101, 102...118, 119, 120.
Yes, yes... I remember elementary school....
We don’t think about it, because we learn this when we are 2–3 years old. Tell a kid to count using an octal system (Base-8) and they will most likely look at you for a few seconds before returning to their homework on their iPad. The theory is easy and identical to the decimal system, we are just not used to it.
Counting in the octal system would look like this:
0,1,2,3,4,5,6,7 → 10,11,12,13,14,15,16,17 → 20,21...
The first conversion we should look at is going from any base (Base-N) to the decimal system(Base-10). It is very easy to convert from The decimal system to any other base. Therefore, it is valuable to know how to get to the decimal system.
If we keep using decimal and octal as examples, we can convert a number in the octal system to its equivalent in the decimal system. Let’s pick a random number.
256(Base-8) → ???(Base-10)
(I promise, 256 was actually random)
We have three positions 2 (hundreds), 5(tens) and 6(ones). These positions can hold between 0–7 (8 numbers)as you remember from the intro.
We need to convert each of the numbers in all positions to the decimal system. The decimal system can hold 0–9 (10 numbers)
The way we do this is that we take each number and multiply it with the base powered by the position index (num * (base^position)). If we start from the lowest number to the right, we multiply 6 with 80, 5 with 81 and 2 with 82.
Here is an illustration that will make this a lot easier to understand:
When we have the number in the decimal system (Base-10) it is really easy to go to any system. Let’s go back from 174 (Base-10) to 256 (Base-8)
We find the number in another base is by dividing it with the base and keep the remainder. The remainders will construct the number in the other base for us. When we have the remainders, we read the number from the least significant number to the most significant number, meaning from bottom to top. There you have your new number represented in your new base! ‍
174/2 gives us 21.75. Notice how 21 is red and .75 is green. We send 21 to the next line and deal with 0.75. If we multiply 0.75 with the base, we are left with the remainder (which is the number we are after). This number is sent to the far right in blue.
When we reach 0, we are officially done and can pack our bags with a beautiful number in our new base.
Not only does this mean we can go from Base-10 to Base-8 and vice versa. We can go from Base-X to Base-Y. Any Base we want.
It feels something like this:
If you are still not sure how this works, I suggest checking out “The Organic Chemistry Tutor”. His videos are the best I know on the subject:
www.youtube.com
It’s time to dive into the code!
Note: I do most of my programming manually. This means that I don’t use too many libraries because I want to learn as much as possible. In this code I do use standard python functions like bin() because I want to show you it’s possible, but instead of using hex(), I think it was more fun to manually code the same functions. In a production environment, you probably want to keep it short and use libraries that solve your problems instead of coding everything from scratch
This program is just one main.py file with all the code in one document. If you wish you can extend this however you like. A Number Class? Sure, why not. GUI? Also something we should look into.
I recommend working with a base converter calculator online while coding to double-check that your code outputs the right values.
Here is the gist:
The logic behind the code is that we pass three arguments to the function convert_number_system():
input_number is the number we want to convert.
input_base is the base we wish to convert from.
output_base is the base we wish to convert to.
I haven’t used multiline string to print menus before, but it would make perfect sense to create a menu like this.
The function returns the whole menu string so we can use it anywhere we want. We don’t have to worry about newline either.
return (    '''----------------------------------------------------------Welcome to the Number Base converter!The system will ask you for:- The Number to convert- What base you want to convert FROM- What base you want to convert TO** REMEMBER: All numbers must be integers!(unless HEX) **----------------------------------------------------------    ''')
Instead of checking the user input in the converter function, I wanted to create validators as functions. By doing that we create code that is re-usable.
This function just checks if the number is a valid binary number.
We don’t want to check a long string with several occurrences of the same number. If we convert it to a set we remove duplicates. Sets can not hold multiple instances of an item. We use list comprehension to convert the input to an int and check it against [0,1]
[int(item) for item in set(list(check_number))]
I chose to pass strings from the user interaction and create integers of the input when needed. int(item) makes sure all the numbers are integers (we could check a string against a string as well (‘0’ , ‘1’) if we wanted to).
This is how it would look behind the scene:
‘10010011’ → [‘1’,’0',’0',’1',’0',’0',’1',’1'] → [0,1]
if 0 is in [0,1] it checks out fine, if 1 is in [0,1], that also checks out fine. If you had a number like 23, both digits would fail and the function would return False
Nothing fancy here... The code checks if the input contains the defined legal characters. We work with numbers 0–9 and because we support HEX, we include a-f as valid input as well.
This function makes use of the other validators and validates all the input so we know if there is a point in proceeding with the conversion.
First it checks if we have entered a number or a hex value.
Then we check the bases. If input_base is 2, we are converting a binary number. Therefore, the input_number must be digits only and if it is, it has to be 0s or 1s.
If the input is both numbers and letters it is a hexadecimal number. If the input_base is NOT 16, then we can’t convert it. HEX is Base-16 and any other base would calculate incorrectly. We could consider making a pre-filled input base during the interaction when someone types in a hexadecimal number.
Last we check if the user tries to convert to- or from a Base-1. That is not possible so instead of crashing the program, we want to return an error.
Now, it is time for the main part of the code. This is where we will make use of our math skills. This function is carrying out the match we looked at earlier, and it has a few safety steps to make sure we output correctly
remainder_list holds the numbers we want to return. If you remember the math you should instantly recognize the keyword remainder.
sum_base_10. We want to use the Base-10 as an intermediate step. We set the initial value to 0 and then we add whatever values we calculate to this variable.
output binary:
if output_base == 2:    return (bin(input_number)[2:])
If the user wants a binary output, we can use the built-in function bin(). We don’t have to because the math is the same no matter what we pass, but it is fun to try.
bin() returns 0b and the binary. The 0b tells us it is a binary number. We want to return the actual number. That’s what the [2:] is for. If you run this in your terminal you can see the difference:
>>> check_number = 23>>> print(bin(check_number))0b10111>>> print(bin(check_number)[2:])10111
It will print from index 2 and onward.
Note that I have left the hex() version in the code, but commented out. You can use it if you want to.
The Base is NOT 10:If the base is not 10, we go through our intermediate step. First, we reverse the list (you don’t have to do this, but then you need to flip the index in the loop later. I find it easier to flip the list.) Reversing lists in Python can be done with this fantastic built-in code [::-1]
reversed_input_number = input_number[::-1]
hex_helper_dict helps us handling numbers above 9 if the user sent a hex input. If you remember our formula, we are multiplying the numbers in each position with the base index powered by position.
Here is 256 again from octal to decimal:
2*(82) + 5*(81) + 6*(80) = 174
If the number is hexadecimal, like 23e, it actually reads 2 , 3, 14
2*(162) + 3*(161) + 14*(160) = 574
This loop helps us with all of that:
for index, number in enumerate(reversed_input_number):    for key,value in hex_helper_dict.items():        if str(number).lower() == key:            number = value    sum_base_10 += (int(number)*(int(input_base)**index))
If you look at the code above, you can see that we use both enumerate and .items() to make sure we can access everything we need to carry out our operation.
By using enumerate, we can access both the value and the index of the variable. When we loop through the variable we check if the number (ex. e) is equal to any of the keys in the dictionary. In the case of 23e, it is and we want to set the number to 14 instead so we can use it for the actual calculation.
sum_base_10 += (int(number)*(int(input_base)**index))
Now that we are sure no numbers are actually letters, we can iterate through the numbers (let’s stay with 256) and do the operations. For each number we loop through, we do the multiplication we talked about earlier.
(int(number)*(int(input_base)**index))6*(8**0)
The values we end up with are added to sum_base_10.
The Base IS 10:Great, carry on. We just assign the input number to sum_base_10:
elif input_base == 10:    sum_base_10 = int(input_number)
Do the mathNow that we have the Base-10 value, we can do the division to find the remainders and find our new number.
while sum_base_10 > 0:    divided = sum_base_10// int(output_base)    remainder_list.append(str(sum_base_10 % int(output_base))    sum_base_10 = divided
We need to divide until we hit 0. The while loop makes sure we do.
By using floor division // we divide the number and are left with the integral part of the quotient. this means we only carry the integer (21) over and not what is after the dot (75) based on our example in the intro (174/8=21.75)
By assigning this number to divided we can also send this value to sum_base_10 for the next iteration in the end.
By using modulo,%, we will get the remainder. This is appended to the remainder_list so we can output it later.
It is a while loop that keeps going until we hit 0
If output base is 16If output_base is 16, that means the user wants a hexadecimal output. We need to convert any number greater than 9 to a letter.
Just as we did earlier, we create a dictionary to help converting. If we find a match, we convert the number to a letter and append it to the list.
And Finally:
return ''.join(remainder_list[::-1])
This reverses and turns our list into a string we can return. If you want to return an int, you can use int(). Make sure you account for Hexadecimal numbers if the user asks for a Base-16 output.
Phew! The majority of our code is done.
The last piece of the puzzle is to interact with the user. It is a straight forward while loop where the user types in numbers, we validate them and run the converter if it validates. The user has the option to do it as many times as he/she wants.
I like to create user interactions with while loops based on the state of a variable; like proceed we have here. This way we can just keep going as long as the user wants.
It has two levels. The first while initiates the main loop based on proceed.lower() == ‘y’.
The second declare valid_input=False , because we want to validate all the input before we pass it to convert_number_system(). As long as the input is False we need new input. After the user gives us his/her input, it runs the validator. If it passes, we can initiate convert_number_system()
In the end, we ask the user if he/she wants to go again. If the user types ‘y’ , we run the code again. If he/she types anything else, we quit the program.
An alternative way to do this is to ask the user to either type the number he/she wants to convert or quit. in the number_input variable.
This program covers several aspects of programming. First, we need to find the problem. The problem is that it takes a lot of time to manually convert all the numbers.
Then we find/learn the math to solve the problem and find what’s common. As it turns out, we can convert anything to anything with the same formula. We only need to handle binary and hexadecimal input and we need to make sure we don’t try to calculate Base-1. This means we can use functions to handle our code.
Everything is wrapped in a menu, a user interaction, and the underlying execution code is lurking in the background.
I hope you learned something by reading this. If you find any issues with the code or have other ideas, please let me know. Feel free to grab the code and continue developing it for your own projects.