Operations, strings and random numbers
Integer division and MOD with negatives, rounding and truncation, XOR, string and date conversions, and pseudo-random numbers.
Do this lesson in the simulatorAt 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)
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))
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)
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")
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
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))
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
- Do the task without
split, finding each comma withfindand taking substrings between them. - What do
-7 // 2,-7 % 2andint(-7 / 2)give? Explain why two of them differ. - Add a command
Wthat waits a number of tenths of a second, soW15waits 1.5 seconds.