Number sets, bases and units

Natural, integer, rational, irrational, real and ordinal numbers; any number base; bits, bytes, kilo and kibi.

A7.1Data representationA level25 min

Covers OCR H446 1.4.1 Data Types · AQA 7517 4.5.1.1 Natural numbers · AQA 7517 4.5.1.2 Integer numbers · AQA 7517 4.5.1.3 Rational numbers · AQA 7517 4.5.1.4 Irrational numbers · AQA 7517 4.5.1.5 Real numbers · AQA 7517 4.5.1.6 Ordinal numbers · AQA 7517 4.5.1.7 Counting and measurement · AQA 7517 4.5.2.1 Number base · AQA 7517 4.5.3.1 Bits and bytes · AQA 7517 4.5.3.2 Units · Eduqas A500QS 2.3 Data representation and data types

Do this lesson in the simulator

At GCSE you converted between binary, denary and hexadecimal and met the units from bits to petabytes. At A level the same ideas come with exact vocabulary: which set a number belongs to, what a base really is, and the difference between a kilobyte and a kibibyte. Examiners mark these words precisely, so this lesson pins them down.

Number sets

Mathematicians group numbers into sets, each written with a letter:

Set Symbol What it holds Examples
Natural numbers ℕ the whole numbers from 0 upwards 0, 1, 2, 51
Integers ℤ whole numbers, positive, negative or zero -128, 0, 127
Rational numbers ℚ any number that can be written as a fraction p/q of two integers, q not 0 3/4, -0.5, 0.1, 7
Irrational numbers real numbers that cannot be written as a fraction √2, π
Real numbers ℝ every rational and irrational number: every point on the number line 0.51, -2.5, π

Each set sits inside the next: every natural number is an integer, every integer is rational (7 is 7/1), and every rational number is real. Irrational numbers are the reals that are not rational. The AQA specification defines ℕ as starting at 0, so learn it that way.

The natural numbers sit inside the integers, the integers inside the rationals, and the rationals and irrationals together make the real numbersℝ real numbersℚ rationalℤ integersℕ natural01251-128-33/4-0.50.1irrationalnot a fraction√2π
Each set sits inside the next, so 51 is natural, an integer, rational (51/1) and real all at once. A number is drawn in the smallest set it belongs to. Only the irrationals, on the right, are real but outside ℚ.

An irrational number's decimal expansion never ends and never repeats. A computer has a fixed number of bits, so it can only ever store an approximation of √2 or π. Lesson A7.4 is about the errors that causes.

Ordinal numbers describe position in an order: first, second, third. When the robot visits waypoints, the waypoint's ordinal tells you which one in the sequence it is, not how many there are. A list index is an ordinal idea: route[0] is the first item.

Counting and measurement

The two sets you use most in programs are chosen by what you are doing:

  • Counting uses natural numbers. The robot has bumped 3 times, there are 64 cells in its ToF grid. You cannot bump 2.7 times.
  • Measurement uses real numbers. The wall is 51.3 cm away, the battery is at 3.82 V. Between any two measurements there is always another possible one.

That is why a counter is an int in Python and a sensor reading is a float. Choosing the wrong one is a design error: a distance stored as an integer silently throws away everything after the point.

Number bases

A base is how many different digits a place can hold, and each place is worth base times the place to its right. In base 10 the places are 1, 10, 100; in base 2 they are 1, 2, 4, 8; in base 16 they are 1, 16, 256.

Base Name Digits 173 written in it
2 binary 0 1 10101101
8 octal 0 to 7 255
10 denary (decimal) 0 to 9 173
16 hexadecimal 0 to 9, A to F AD

When bases are mixed, write the base as a subscript: 10101101₂ = 173₁₀ = AD₁₆.

To convert from any base, multiply each digit by its place value and add. To convert to any base, divide by the base repeatedly: each remainder is a digit, starting with the rightmost.

173 to hexadecimal: 173 ÷ 16 = 10 remainder 13 (D), then 10 ÷ 16 = 0 remainder 10 (A). Reading the remainders from last to first gives AD.

173 converted from binary by adding place values, and to hexadecimal by dividing by 16 again and againfrom base 2: multiply each digit by its place value and addplace value1286432168421binary1111100012832841128 + 32 + 8 + 4 + 1 = 173to base 16: divide by 16, keep each remainder as a digit173 ÷ 16 = 10remainder 13digit D10 ÷ 16 = 0remainder 10digit Aread the digitsfrom the bottom up173 = AD₁₆stop when the answer to the division is 0
Two directions, two methods. Into denary, add the place values under the 1s. Out of denary, divide repeatedly: the first remainder is the rightmost digit, which is why the digits are read upwards.
# place values: any base back to denary
DIGITS = "0123456789ABCDEF"

def from_base(text, base):
    value = 0
    for ch in text:
        value = value * base + DIGITS.index(ch)   # shift one place left, add the new digit
    return value

print(from_base("10101101", 2), from_base("255", 8), from_base("AD", 16))
print(int("AD", 16))           # Python's built-in does the same job

Run this in the simulator

Hexadecimal is used as a shorthand for binary because 16 is 2⁴: each hex digit stands for exactly four bits. Colour codes, memory addresses and the bytes in a radio packet are all easier to read and harder to mistype in hex. The computer still stores binary; hex is for people.

10101101 split into two groups of four bits, 1010 and 1101, which are the hex digits A and D842111008 + 2 = 10A842111108 + 4 + 1 = 13D|
Because 16 = 2⁴, each hex digit replaces exactly four bits, and the groups can be converted separately without ever working out 173. Start grouping from the right.

Bits, bytes and units

A bit is one binary digit. A byte is a group of 8 bits. With n bits you can make 2ⁿ different patterns, so 8 bits give 256 and 16 bits give 65,536. Turned the other way: to give each of 64 ToF cells its own number you need 6 bits, since 2⁶ = 64.

Storage sizes use two families of prefixes, and they are not the same:

Decimal prefix Value Binary prefix Value
kilo (kB) 10³ = 1,000 kibi (KiB) 2¹⁰ = 1,024
mega (MB) 10⁶ mebi (MiB) 2²⁰
giga (GB) 10⁹ gibi (GiB) 2³⁰
tera (TB) 10¹² tebi (TiB) 2⁴⁰

The binary prefixes exist because memory comes in powers of two and people used "kilobyte" loosely for both values. The difference grows with size: 1 GiB is about 7% more than 1 GB, and 1 TiB about 10% more than 1 TB. That is why a "1 TB" drive shows as about 931 GiB.

How many per cent bigger each binary prefix is than the decimal one: 2.4, 4.9, 7.4 and 10.0how much bigger the binary prefix iskibi / kilo2.4%mebi / mega4.9%gibi / giga7.4%tebi / tera10.0%a 1 TB drive holds 10¹² bytes = 931 GiB
The gap is 2.4% at kilo and grows with every step, because each binary prefix multiplies by 1,024 where the decimal one multiplies by 1,000. By tera it is 10%, which is why a new drive looks smaller than the box says.
frame_bits = 320 * 240 * 24      # one camera frame at 24 bits per pixel
frame_bytes = frame_bits // 8
print(frame_bytes, "bytes")
print(frame_bytes / 1000, "kB", frame_bytes / 1024, "KiB")
print(10 ** 12 / 2 ** 30, "GiB in a terabyte")

Run this in the simulator

Task: any base

Write to_base(n, base). The parameter n is a whole number, 0 or more; base is a whole number from 2 to 16. It returns the digits of n in that base as a string, using 0 to 9 then A to F, and returns "0" when n is 0. Use repeated division with // and %: you may not use bin, hex, oct or format. The loop at the bottom prints lines such as 173 in base 16 = AD.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

DIGITS = "0123456789ABCDEF"

def to_base(n, base):
    # repeated division goes here
    return ""

for n, base in [(173, 2), (173, 8), (173, 16), (0, 2), (2024, 16)]:
    print(n, "in base", base, "=", to_base(n, base))

Task: kilo or kibi

The robot's camera sees 320 by 240 pixels at 24 bits per pixel. Using the four constants, calculate and print exactly these five lines (the numbers must be calculated, not typed):

  • frame: <bytes> bytes, the size of one frame in bytes, as a whole number
  • frame: <n> kB, bytes divided by 1,000, then round(value, 2)
  • frame: <n> KiB, bytes divided by 1,024, then round(value, 2)
  • minute at 30 fps: <n> MB, the bytes in 60 seconds of frames divided by 1,000², then round(value, 2)
  • minute at 30 fps: <n> MiB, the same bytes divided by 1,024², then round(value, 2)
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

WIDTH = 320
HEIGHT = 240
BITS_PER_PIXEL = 24
FPS = 30

frame_bytes = 0
print("frame:", frame_bytes, "bytes")

Challenges

  1. Extend to_base to refuse a base outside 2 to 16 by raising a ValueError.
  2. Write from_base without DIGITS.index, using character codes instead.
  3. Which set does each belong to: the number of robots on the mat, the robot's heading in degrees, 22/7, the square root of 16?