Operations, strings and random numbers

Integer division and MOD with negatives, rounding and truncation, XOR, string and date conversions, and pseudo-random numbers.

A1.2Programming techniques and object-oriented programmingA level20 min

Do this lesson in the simulator

At GCSE you used the arithmetic operators (F1.9), and, or and not (F2.3), string handling (F3.1) and randint (F3.7). A level adds the details that trip people up: what integer division does with negative numbers, the difference between rounding and truncating, exclusive or, converting between strings, numbers and dates, and why random numbers from a computer are not really random.

Arithmetic

Operation Python 17 and 5 -17 and 5
Addition + 22 -12
Subtraction - 12 -22
Multiplication * 85 -85
Real (float) division / 3.4 -3.4
Integer division // 3 -4
Remainder % 2 3
Exponentiation ** 1419857 -1419857

AQA's pseudo-code and OCR's reference language both write integer division as DIV and the remainder as MOD.

Look at the last column. Python's // rounds down, towards minus infinity, so -17 // 5 is -4, and % gives a remainder with the same sign as the divisor, so -17 % 5 is 3. The two always agree: 5 * -4 + 3 is -17. Some languages round integer division towards zero instead and would give -3 and -2, so for negative numbers check the language before you trust an answer.

That behaviour is exactly what a robot heading needs. Turning left 90 degrees from heading 0 should give 270, not -90:

heading = 0
heading = (heading - 90) % 360
print(heading)
heading = (heading + 135) % 360
print(heading)

Run this in the simulator

Rounding gives the nearest value to some number of places; truncation chops the fractional part off. They differ for 3.7, and for negative numbers truncation and rounding down differ too:

import math

print(round(3.7), int(3.7), math.floor(3.7))       # round, truncate, round down
print(round(-3.7), int(-3.7), math.floor(-3.7))
print(round(2.5), round(3.5))                      # halves go to the even number
print(round(31.46, 1))

Run this in the simulator

Python's round sends an exact half to the nearest even whole number, so round(2.5) is 2. That surprises people who expect halves always to round up.

Relational and Boolean operations

The relational operators compare two values and give a Boolean: ==, !=, <, >, <= and >=. One warning at A level: reals are stored in binary, so some decimals are not exact, and == on reals can fail:

print(0.1 + 0.2 == 0.3)
print(abs((0.1 + 0.2) - 0.3) < 0.000001)

Run this in the simulator

Compare reals with a tolerance, as the second line does.

The Boolean operators are NOT, AND, OR and XOR, exclusive or: true when exactly one side is true.

A B A AND B A OR B A XOR B
False False False False False
False True False True True
True False False True True
True True True True False

Python has no xor keyword. For two Booleans, a != b is XOR, and so is a ^ b. A robot that should beep when the wall is close on exactly one side, left or right but not both, uses XOR:

left_close = True
right_close = False
print("beep" if left_close != right_close else "quiet")

Run this in the simulator

String handling

Operation Python AQA pseudo-code s = "F20,R90"
Length len(s) LEN(s) 7
Position of a character s.find(",") POSITION(s, ',') 3
Substring s[1:3] SUBSTRING(1, 2, s) "20"
Concatenation s + ",F5" s + ',F5' "F20,R90,F5"
Character to code ord("F") CHAR_TO_CODE('F') 70
Code to character chr(82) CODE_TO_CHAR(82) "R"
String to integer int("20") STRING_TO_INT('20') 20
String to real float("31.5") STRING_TO_REAL('31.5') 31.5
Integer or real to string str(20) INT_TO_STRING(20) "20"

Watch the substring. Python's slice s[1:3] starts at index 1 and stops before index 3; AQA's SUBSTRING(1, 2, s) gives the characters from position 1 to position 2 inclusive. Both give "20".

Dates and times are converted with a format: codes such as %d for day and %H for hour say where each part goes.

from datetime import datetime

logged = datetime.strptime("14/09/2026 09:30", "%d/%m/%Y %H:%M")   # string to date/time
print(logged.year, logged.hour)
print(logged.strftime("%Y-%m-%d"))                                  # date/time to string

Run this in the simulator

Random number generation

A computer follows instructions exactly, so it cannot make a truly random number by calculation alone. A pseudo-random generator produces a sequence that looks random from a starting value called the seed. The same seed always gives the same sequence, which is how a task checks a program that rolls dice:

import random

random.seed(42)
print(random.randint(1, 6), random.randint(1, 6), random.randint(1, 6))
random.seed(42)
print(random.randint(1, 6), random.randint(1, 6), random.randint(1, 6))

Run this in the simulator

Both lines print the same three numbers. Without a seed, Python starts from something that changes, such as the time. In AQA's pseudo-code, RANDOM_INT(1, 6) gives a whole number from 1 to 6 inclusive, like randint.

Task: drive a command string

A route arrives as one string of commands separated by commas: COMMANDS = "F20,L90,F15,R135,B5". Each command is a letter and a whole number: F drives forward that many cm, B drives backward that many cm, R turns right that many degrees and L turns left that many degrees.

Go through the string and carry out each command on the robot. Keep two totals as you go: the distance driven in cm (forward and backward both add) and the heading in degrees, starting at 0, turning right adding and turning left subtracting, always kept from 0 to 359 with MOD 360. Convert each amount with int. At the end print driven <cm> cm, heading <degrees>, for this string driven 40 cm, heading 45. Work both totals out from the string; do not type them.

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

COMMANDS = "F20,L90,F15,R135,B5"

for command in COMMANDS.split(","):
    print(command)

Challenges

  1. Do the task without split, finding each comma with find and taking substrings between them.
  2. What do -7 // 2, -7 % 2 and int(-7 / 2) give? Explain why two of them differ.
  3. Add a command W that waits a number of tenths of a second, so W15 waits 1.5 seconds.