Integer Conversion Programs

You can read this note directly if you know variables, loops, integer division, strings, and functions.

The syllabus expects programs that convert positive integers between denary, binary, and hexadecimal, and display the results. The important idea is not to memorise a special trick. The program is the manual conversion method written as a loop.

Big Picture

A number can have different written representations.

For example:

45_denary = 101101_binary = 2D_hex

The value is the same. The representation is different.

A conversion program takes one representation and produces another representation. In many beginner programs, the output binary or hexadecimal value is stored as a string, such as "101101" or "2D", because it is a sequence of digits written in another base.

Why the Output Is Usually a String

In Python, the denary integer 101101 is not the same thing as the binary representation 101101_binary.

If a function returns the integer 101101, Python treats it as one hundred and one thousand, one hundred and one in denary. That is not the value forty-five.

Therefore, a conversion function usually returns a string:

"101101"

This string is a written representation of the value in binary. It is not meant to be used directly as a denary integer.

The same applies to hexadecimal. A result such as "2F4" must be a string because it contains the symbol F.

What the Program Is Doing

The program version follows the same logic as manual conversion.

Manual actionProgram action
divide by the target baseuse n // base
write down the remainderuse n % base
convert the remainder into a digituse str(remainder) or HEX_DIGITS[remainder]
put the newest digit on the leftprepend to the result string
stop when the quotient becomes 0loop while n > 0

The code is not magic. It automates the repeated-division table.

Caption: A repeated-division table becomes a loop: use n % base for the next digit, prepend it to the result string, then use n // base for the next value of n.

Key Operators

Python gives two useful integer operators:

OperatorMeaningExample
//integer quotient45 // 2 gives 22
%remainder45 % 2 gives 1

Repeated division uses both. The remainder gives the next digit. The quotient becomes the next number to divide.

For example:

45 // 2 = 22
45 % 2 = 1

This means:

45 = 22 x 2 + 1

The remainder 1 is the lowest-value binary digit.

General Pattern: Denary to Another Base

The same algorithm works for converting a non-negative denary integer to binary or hexadecimal.

Pseudocode:

if n is 0:
    return "0"
 
result <- ""
while n > 0:
    remainder <- n MOD base
    digit <- digit symbol for remainder
    result <- digit + result
    n <- n DIV base
 
return result

The newest digit is added to the left because repeated division finds the lowest-value digit first.

For binary, the base is 2.

For hexadecimal, the base is 16.

Denary to Binary

This function takes a non-negative integer and returns a string because binary output is a representation, not a normal denary integer.

def denary_to_binary(n):
    if n == 0:
        return "0"
 
    bits = ""
    while n > 0:
        remainder = n % 2
        bits = str(remainder) + bits
        n = n // 2
    return bits

Trace for denary_to_binary(45):

n before divisionn % 2bits after prependingnext n
451122
2200111
1111015
5111012
20011011
111011010

Result:

45_denary = 101101_binary

Why Prepend Instead of Append?

The first remainder is the ones bit. The next remainder is the twos bit. Then comes the fours bit, and so on.

So the remainders are discovered from right to left, but the final binary representation is written from left to right.

That is why the code uses:

bits = str(remainder) + bits

not:

bits = bits + str(remainder)

If you append instead of prepend, the result will usually be reversed.

Try This

Trace denary_to_binary(13).

Expected result:

13_denary = 1101_binary

General Pattern: Another Base to Denary

To convert a string representation back to denary, process the digits from left to right.

Pseudocode:

total <- 0
for each digit from left to right:
    digit_value <- value of digit
    total <- total * base + digit_value
 
return total

This works because reading a new digit on the right shifts the previous value one place to the left.

In base 2, shifting one place left means multiplying by 2.

In base 16, shifting one place left means multiplying by 16.

More generally, in base :

Binary to Denary

This version processes the string from left to right.

def binary_to_denary(bits):
    total = 0
    for bit in bits:
        total = total * 2 + int(bit)
    return total

Trace for binary_to_denary("101101"):

Next bitCalculationNew total
10 * 2 + 11
01 * 2 + 02
12 * 2 + 15
15 * 2 + 111
011 * 2 + 022
122 * 2 + 145

The invariant is:

After each step, total is the denary value of the bits processed so far.

For example, after processing 101, the total is 5 because:

101_binary = 5_denary

Why This Method Is Useful

This left-to-right method avoids writing powers of 2 explicitly. It is also easy to adapt to hexadecimal by changing the base from 2 to 16.

Try This

Trace binary_to_denary("1101").

Expected result:

13

Denary to Hexadecimal

A remainder from 0 to 15 must be converted to a hexadecimal digit.

HEX_DIGITS = "0123456789ABCDEF"
 
 
def denary_to_hex(n):
    if n == 0:
        return "0"
 
    digits = ""
    while n > 0:
        remainder = n % 16
        digits = HEX_DIGITS[remainder] + digits
        n = n // 16
    return digits

The string HEX_DIGITS is used as a lookup table.

RemainderHex digit
00
11
99
10A
11B
15F

Example:

print(denary_to_hex(756))

Output:

2F4

Trace for denary_to_hex(756):

n before divisionn % 16Hex digitdigits after prependingnext n
75644447
4715FF42
2222F40

Result:

756_denary = 2F4_hex

Hexadecimal to Denary

HEX_DIGITS = "0123456789ABCDEF"
 
 
def hex_to_denary(hex_string):
    total = 0
    for digit in hex_string.upper():
        value = HEX_DIGITS.index(digit)
        total = total * 16 + value
    return total

Example:

print(hex_to_denary("2F4"))

Output:

756

Trace for hex_to_denary("2F4"):

Next digitDigit valueCalculationNew total
220 * 16 + 22
F152 * 16 + 1547
4447 * 16 + 4756

The call to .upper() makes lowercase input work too:

print(hex_to_denary("2f4"))

Output:

756

Binary and Hexadecimal Using Denary as a Bridge

Once binary-to-denary and denary-to-hexadecimal are correct, binary-to-hexadecimal can use both:

def binary_to_hex(bits):
    denary = binary_to_denary(bits)
    return denary_to_hex(denary)

Similarly:

def hex_to_binary(hex_string):
    denary = hex_to_denary(hex_string)
    return denary_to_binary(denary)

This is simple and clear. A manual conversion question may expect grouping into four bits, but a program can use correct helper functions if the question allows it.

Program Method Versus Manual Method

There are two common ways to convert between binary and hexadecimal.

SituationSuitable method
Manual calculationgroup binary bits into groups of four
Simple beginner programconvert through denary using helper functions
More specialised programprocess four-bit groups directly

For exam programs, clarity is important. If helper functions are already correct, using denary as a bridge is often the safest beginner method.

A Small Display Program

This is a complete example that displays all three representations for one positive integer:

def show_representations(n):
    print("Denary:", n)
    print("Binary:", denary_to_binary(n))
    print("Hexadecimal:", denary_to_hex(n))
 
 
show_representations(45)

Output:

Denary: 45
Binary: 101101
Hexadecimal: 2D

This style is useful in exam programs because conversion and display are separated: one function calculates a result, and another part of the program prints it.

This makes the code easier to test. For example, you can test denary_to_binary(45) without also testing the display format.

Input Validation

The syllabus focuses on positive integer conversion, but a robust program should still reject invalid input.

def is_binary_string(bits):
    if bits == "":
        return False
    for bit in bits:
        if bit not in "01":
            return False
    return True

A similar check can be used for hexadecimal:

def is_hex_string(hex_string):
    if hex_string == "":
        return False
    for digit in hex_string.upper():
        if digit not in "0123456789ABCDEF":
            return False
    return True

Test cases for is_binary_string():

InputExpected resultReason
"101101"Trueall characters are binary digits
"10201"False2 is not a binary digit
""Falseempty input is not a valid representation
"abc"Falseletters are not binary digits

Test cases for is_hex_string():

InputExpected resultReason
"2F4"Trueall characters are hexadecimal digits
"2f4"Truelowercase is accepted after .upper()
"G1"FalseG is not a hexadecimal digit
""Falseempty input is not a valid representation

Safer Conversion Functions With Validation

The simple conversion functions above assume the input is valid. In a larger program, it is safer to validate before converting.

def safe_binary_to_denary(bits):
    if not is_binary_string(bits):
        return None
    return binary_to_denary(bits)

Example:

print(safe_binary_to_denary("101101"))
print(safe_binary_to_denary("10201"))

Output:

45
None

Using None is one simple way to show that conversion failed. A larger program could instead print an error message or ask the user to enter the value again.

Test Cases for Conversion Functions

Good programs should be tested with more than one example.

Function callExpected resultWhat it tests
denary_to_binary(0)"0"boundary case
denary_to_binary(45)"101101"ordinary binary conversion
binary_to_denary("101101")45reverse of previous conversion
denary_to_hex(756)"2F4"hexadecimal digit above 9
hex_to_denary("2F4")756uppercase hex input
hex_to_denary("2f4")756lowercase hex input
binary_to_hex("101101")"2D"binary to hexadecimal via denary
hex_to_binary("2D")"101101"hexadecimal to binary via denary

A useful checking habit is to test conversions in pairs:

n = 45
bits = denary_to_binary(n)
print(binary_to_denary(bits))

The final printed value should be the original n.

Trace-Table Habit

When checking a conversion program, do not only read the code. Trace the variables.

For repeated division, useful columns are:

VariableMeaning
n before divisioncurrent value still to convert
n % basenext digit value
result stringrepresentation built so far
n // basenext value of n

For base-to-denary conversion, useful columns are:

VariableMeaning
next digitdigit currently being processed
digit valuedenary value of that digit
calculationupdate rule
totaldenary value of processed prefix

Tracing is especially useful for finding off-by-one loop errors and reversed-output errors.

Common Mistakes

MistakeWhy it is wrongHow to fix it
Using / instead of /// produces a real number, not an integer quotientuse // for quotient
Appending remainders to the rightrepeated division finds low-value digits firstprepend each new digit
Forgetting n == 0the loop would return an empty stringreturn "0" as a special case
Returning binary as an integerPython will treat it as a denary integerreturn a string such as "101101"
Accepting invalid binary stringsstrings such as "1021" are not binaryvalidate before converting
Treating hex input as case-sensitivea and A usually mean the same hex digitcall .upper()
Forgetting to convert hex lettersF cannot be used directly in arithmeticuse HEX_DIGITS.index(digit)

Enrichment: One Function for Bases up to 16

The binary and hexadecimal functions are very similar. This suggests a more general function.

DIGITS = "0123456789ABCDEF"
 
 
def denary_to_base(n, base):
    if base < 2 or base > 16:
        return None
 
    if n == 0:
        return "0"
 
    result = ""
    while n > 0:
        remainder = n % base
        result = DIGITS[remainder] + result
        n = n // base
    return result

Examples:

print(denary_to_base(45, 2))
print(denary_to_base(756, 16))

Output:

101101
2F4

This is enrichment. For the core syllabus, it is enough to understand the binary and hexadecimal versions clearly.

Check Your Understanding

  1. Why does denary_to_binary() return a string instead of an integer?
  2. Why does denary_to_binary() prepend the new bit?
  3. What does n % 16 represent in denary-to-hexadecimal conversion?
  4. What invariant is used by binary_to_denary()?
  5. Why should hex_to_denary() call .upper() on the input string?
  6. What is the output of denary_to_binary(13)?
  7. What is the output of hex_to_denary("2F4")?
  8. Why is input validation useful before calling binary_to_denary(bits)?

Answers:

  1. Because binary output such as "101101" is a representation. If it were returned as the integer 101101, Python would treat it as a denary integer.
  2. Repeated division finds the low-value digit first, so new remainders must be placed on the left of the existing result.
  3. The next hexadecimal digit value.
  4. total is the denary value of the processed prefix.
  5. So lowercase and uppercase hexadecimal letters are handled consistently.
  6. "1101".
  7. 756.
  8. It prevents invalid inputs such as "1021" from being treated as if they were valid binary representations.