The following is one of my notes from the course ITI 1100 (Digital Systems) (taken at the University of Ottawa during the winter of 2017). It describes how to convert numbers in any base to base-10 and back again. The course emphasised hand calculation methods (the kind needed on an exam), but I found writing code helped with the learning process.

# Base Conversions¶

## Converting any Base to Base 10 (i.e. Decimal)¶

Given a number and its base, converting to decimal can be achieved by expanding the number into a sum of terms. Each term is the digit multiplied by the base of the number to the exponent of the place value of the digit -1. Written instructions for this process are hard to follow; analyzing examples and trying to write a function to do the conversion helped me get a better handle on how to do these conversions.

For example, converting the binary number "111" into decimal:

\begin{align} (111)_2 &= (1 \times 2^2) + (1 \times 2^1) + (1 \times 2^0) \\ &= 4 + 2 + 1 \\ &= 7 \\ \end{align}

Python code:

In [2]:
import math

def toBase10(n, b):
'''Converts a number (n) in base (b) to base 10'''

# convert n into a list where each element is a digit in n
# e.g. 111 -> [1, 1, 1]
nS = str(n)
nL = [int(x) for x in nS]

result = 0
i = len(nL) - 1  # i represents the place value of the digit - 1
for digit in nL:
result+= digit * b**(i)
i -= 1
return result

In [3]:
toBase10(111, 2)

Out[3]:
7

## Converting Between Binary, Octal, Decimal, and Hex¶

### Whole Number Conversion¶

Converting from a higher base to a lower base is more involved.

One method is to divide the number we are converting by the desired base over and over again until the integer quotient reaches 0. The digits of the converted number can be derived by the value of the remainders (with the hitch that the digits will be backwards unless read from bottom to top).

Example from "Digital Design" by Mano and Ciletti depicting the conversion of 41 (in base 10) to base 2:

The same example, $(41)_{10} \rightarrow (?)_2$, with Python:

In [4]:
n = 41
digits = []

# Conversion is complete when n has been integer divided down to 0
while (n != 0):
# Each digit is the remainder of n // 2
digits.append(n % 2)
n = n // 2

# digits are in reverse order, so set them right
digits.reverse()
digits

Out[4]:
[1, 0, 1, 0, 0, 1]

So $(41)_{10} \rightarrow (101001)_2$.

### Fractional Conversion¶

Now, the above only works for whole numbers. If the number being converted has a fractional part, then the operation becomes a two-step process.

1. Convert the whole part using the method described previously.
2. Convert the fractional part using the method described next.

For fractions, we take the fractional part of the number and multiply it by the desired base--this returns an integer and a new fraction. The integer represents the first place digit of the converted number while the fraction can either be discarded or multiplied again to return another integer/fraction pair. Sometimes the fractional part will end as 0.0 and the process will terminate, but most of the time the process can be continued indefinitely (for arbitrary precision).

Another Mano & Cilleti example:

So the answer is $(0.6875)_{10} = (0.a_1 a_2 a_3 a_4)_2 = (0.10110)_2$.

Now to do it with code. Note that the fractional part will be represented by a float; this is probably not the best way to do it, but it was enough for my purposes.

e.g. $(41.23)_{10} \rightarrow (101001.?)_2$.

The whole part was determined earlier, so only the fractional part needs to be converted.

In [5]:
# The decimal fraction we are converting
n = 0.23

temp = []

# Storage for converted digits
digits = []

# The method will be repeated four times
for i in range(4):
# multiply by destination base
n = n * 2

# split into fractional and integer parts
n_split = math.modf(n)
n_fractional = n_split[0]
n_integer = n_split[1]

# Save digit and set n to the fractional part
digits.append(n_integer)
n = n_fractional

digits

Out[5]:
[0.0, 0.0, 1.0, 1.0]

So $(41.23)_{10} \rightarrow (101001.0011)_2$.

Splitting $n$ into fractional and integer parts is accomplished with modf(), a nifty function from Python's math library.

Another example with a different base:

$$(0.513)_{10} \rightarrow (0.?)_8$$
In [8]:
# The decimal we are converting
n = 0.513

# Storage for converted digits
digits = []

# The method will be repeated six times
for i in range(6):
# multiply by destination base (8 this time!)
n = n * 8

# split into fractional and integer parts
n_split = math.modf(n)
n_fractional = n_split[0]
n_integer = n_split[1]

# Save digit and set n to the fractional part
digits.append(n_integer)
n = n_fractional

digits

Out[8]:
[4.0, 0.0, 6.0, 5.0, 1.0, 7.0]
$$(0.513)_{10} \rightarrow (0.406517)_8$$

### Binary, Octal, and Hex Conversion¶

There is a shortcut for converting between binary, octal, and hexadecimal numbers. Since octal and hex have a base that is a power of two, conversion can be done by grouping.

The following methods are based on the fact that each octal digit corresponds to three binary digits and each hexadecimal digit corresponds to four binary digits.

#### Binary -> Octal¶

$$(10111110010.001)_2 \rightarrow (?)_8$$

Break up into groups of three (tip: starting from right-most digit makes this eaiser).

$$10 \ 111 \ 110 \ 010 \ . \ 001$$

If a group lacks three digits, add a 0 to the left-most place.

$$010 \ 111 \ 110 \ 010 \ . \ 001$$

Now convert each 3-bit group into its octal equivalent (e.g $(010)_2 = (2)_8$).

$$2 \ 7 \ 6 \ 2 \ . \ 1$$

The conversion is complete.

$$(10111110010)_2 \rightarrow (2762.1)_8$$

#### Binary -> Hex¶

Binary to hexadecimal is handled the same way as Octal, except with groups of four instead of three.

$$(10111110010.001)_2 \rightarrow (?)_{16}$$

Break up into groups of four.

$$0101 \ 1111 \ 0010 \ . \ 0010$$

Note that the fractional group had a zero concatenated from the right instead of the left (as with groups from the whole part). Remember that we do not want to change the value, just create groups. Adding a zero to the left of a whole number does not change its value ($125 = 0000125$), but this is not true once we move past the decimal point $(0.5 \ne 0.0005)$. This is obvious, but it's a silly mistake I made typing this out.

Now convert each 4-bit group into its hexadecimal equivalent. I like to convert to decimal first.

$$5 \ 15 \ 2 \ . \ 2$$$$5 \ F \ 2 \ . \ 2$$

The conversion is complete.

$$(10111110010)_2 \rightarrow (5F2.2)_{16}$$

#### Octal/Hex -> Binary¶

Converting to binary from octal or hex is just the same procedure described above except done backwards. Starting with an octal/hex value, convert each digit into its binary equivalent, and then concatenate the binary groups together.