BACK Mascot image.
← MA0 2 · Introduction to Algorithms and Numerical Analysis

Lesson 3

Introduction to Python

Taught

Imperative Knowledge and Computation

There are two kinds of knowledge a mathematical text can record. Declarative knowledge is a statement of fact: the square root of a positive real xx is the positive yy with y2=xy^2 = x. Imperative knowledge is a recipe: a sequence of instructions which, followed to the letter, produces it. A machine can carry out imperative knowledge, but not declarative knowledge.

At its lowest level a computer does two things: it performs arithmetic, and it stores the results. It does billions of operations a second and can store a great deal.

Definition 3.1 (Algorithm).

An algorithm is a finite sequence of unambiguous instructions which, given an initial state and a set of inputs, passes through well-defined successive states and after finitely many steps produces an output and stops.

Finite rules out an infinite list of instructions. Unambiguous rules out a step whose meaning depends on who is reading it. After finitely many steps rules out a recipe that never returns an answer; of the three conditions it is the hardest to check.

Writing an algorithm down needs a notation for assignment, which has no counterpart in an equation. We write

g=defeg \defeq e

for the instruction “replace the current value of gg by the value of the expression ee”. It is an instruction, not a statement about gg. The instruction g=defg+1g \defeq g + 1 makes sense, while the equation g=g+1g = g + 1 has no solutions.

Here is Heron of Alexandria’s method for the square root of a real x>0x > 0.

  1. Choose any guess g>0g > 0.
  2. If g2g^2 is close enough to xx, stop and return gg.
  3. Otherwise perform g=def12(g+x/g)g \defeq \tfrac{1}{2}\bigl(g + x/g\bigr).
  4. Go back to step 2.
startg ≝ 1|g² − x| < εreturn gstopg ≝ (g + x/g)/2yesno
Figure 3.1. Heron’s method as a flowchart. The test in the diamond decides which arrow is followed, and the arrow from the update box returns to the test, so the accented box may run any number of times.

The procedure has the two ingredients of every algorithm: a list of operations, and control flow, the rule deciding which operation comes next. Control flow depends on tests that answer yes or no, and it lets a fixed piece of text describe a computation whose length is not fixed.

Problem 3.1.

Let x=25x = 25 and g=1g = 1, and read “close enough” as ∣g2−x∣<0.01|g^2 - x| < 0.01. Compute the first three values of gg produced by Heron’s method. Does the method stop within those three steps?

Computability

Early machines were fixed-program computers, wired to solve one problem. The stored-program computer keeps the instructions and the data they act on in the same memory, so that a single interpreter can execute any legal instruction sequence handed to it. A program counter walks the interpreter through the instructions in order, deviating only where control flow says to jump. Since the output of a computation may itself be a sequence of instructions, a machine of this kind can write its own programs.

Remark (The Church–Turing thesis).

Turing’s 1936 model, the Universal Turing Machine, is the standard formalisation of what a stored-program machine can do. The Church–Turing thesis asserts that a function is computable by any effective procedure exactly when some Turing machine computes it. It is not a theorem: “effective procedure” is an informal notion, and the thesis says that the formal model captures it.

A language is Turing complete if it can simulate a Universal Turing Machine. Python is, and so is every language in ordinary use, which is why any algorithm expressible in one of them is expressible in all of them.

There is, however, no algorithm which, given an arbitrary program and its input, decides in finitely many steps whether that program eventually stops or runs forever. This is the halting problem. Because it is unsolvable, the clause “stops after finitely many steps” in the definition of an algorithm has to be proved for each algorithm separately; it cannot be checked mechanically.

Syntax and Semantics

A program is a piece of text, and like a mathematical formula it has a meaning only if it obeys three kinds of rule.

  1. Primitives. The atoms of the language: numeric literals such as 3.2, strings of text, and operators such as + and *.
  2. Syntax. The rules saying which arrangements of primitives are well formed. 3.2 + 3.2 is well formed; 3.2 3.2 is not. Violations are caught before a single instruction runs.
  3. Static semantics. The rules saying which well-formed arrangements have a meaning. Adding a number to a piece of text has the right shape, operand-operator-operand, and still means nothing.

A program that obeys all three has a semantics: exactly one meaning, fixed by the language and not by the reader. An English sentence, by contrast, can have several.

So when a program misbehaves the machine has not misunderstood it: the error is in what was written. It shows up in one of three ways: the program stops with an error, it runs forever, or it finishes and returns the wrong answer. The third gives no sign that anything is wrong, which is why a program needs a proof of correctness and not only a run that looked right.

Problem 3.2.

Classify each of the following as a syntax error, a static semantic error, or a program that runs to completion and returns the wrong answer. Justify each answer in a sentence.

  1. x = 5 + * 3
  2. x = "hello" + 7
  3. An implementation of Heron’s method that stops as soon as g2>xg^2 > x rather than when ∣g2−x∣|g^2 - x| is small.

Problems, Words and Languages

To compare algorithms for a problem, the problem has to be stated in a fixed form. A machine reads and writes finite strings of characters, so problems are stated in terms of strings.

Definition 3.2 (Alphabet, word and language).

Let AA be a non-empty finite set, called an alphabet. For k∈N0k \in \mathbb{N}_0 let AkA^k denote the set of functions {1,…,k}→A\{1, \ldots, k\} \to A. Such a function ff is written as the sequence

f(1) f(2)⋯f(k)f(1)\,f(2)\cdots f(k)

and is called a word, or string, of length kk over AA. The set A0A^0 has a single element, the empty word, of length 00. Writing

A∗=⋃k∈N0AkA^{*} = \bigcup_{k \in \mathbb{N}_0} A^{k}

for the set of all words over AA, a language over AA is a subset of A∗A^{*}.

A set SS is finite if there is an injection S→{1,…,n}S \to \{1, \ldots, n\} for some n∈Nn \in \mathbb{N}, and infinite otherwise; the number of elements of a finite set is written ∣S∣|S|. A set admitting an injection into N\mathbb{N} is countable, and the infinite ones among them are the countably infinite sets of the last lesson.

Definition 3.3 (Computational problem).

A computational problem is a relation P⊆D×EP \subseteq D \times E such that every d∈Dd \in D has at least one e∈Ee \in E with (d,e)∈P(d, e) \in P. The elements of DD are the instances of PP, and ee is a correct output for the instance dd whenever (d,e)∈P(d, e) \in P.

The problem is unique if PP is a function, so that every instance has exactly one correct output. It is discrete if DD and EE are languages over a finite alphabet, and numerical if D⊆RmD \subseteq \mathbb{R}^m and E⊆RnE \subseteq \mathbb{R}^n for some m,n∈Nm, n \in \mathbb{N}. A unique problem P:D→EP : D \to E with ∣E∣=2|E| = 2 is a decision problem.

A decision problem is one whose answer is yes or no, and we state such a problem by naming its instances and asking the question.

Example 3.4 (Primality as a decision problem).

Taking N\mathbb{N} as a language over the alphabet {0,1,…,9}\{0, 1, \ldots, 9\}, the relation

{ (n,e)∈N×{0,1}  ∣  e=1 exactly when n is prime }\bigl\{\, (n, e) \in \mathbb{N} \times \{0, 1\} \;\bigm|\; e = 1 \text{ exactly when } n \text{ is prime} \,\bigr\}

is a decision problem, which we write as

Primality
Input:    n ∈ N.
Question: is n prime?

A machine works directly only on discrete problems. A numerical problem must have its instances and its answers written as finite strings, and no finite string names an arbitrary real; rounding error is the difference between a real number and the string used for it.

Pseudocode

An algorithm does not depend on a programming language, and is usually written in pseudocode: the control flow and the assignments written out, without the details a particular language would require. An algorithm in pseudocode names its input and its output, and uses =def\defeq for assignment and output for the result.

Example 3.5 (The square of an integer).

Squaring a natural number takes a single instruction, and the algorithm written out in full reads

The Square of an Integer
Input:  x ∈ N.
Output: the square of x.

    result ≝ x · x
    output result

The variables here are the input x and the working variable result; the assignment puts the value of x⋅xx \cdot x into result, and output yields what result holds.

We use pseudocode to describe an algorithm and Python to run it.

Objects and Types

A Python program is a sequence of definitions and commands, evaluated in order by an interpreter. A command is called a statement, and it instructs the interpreter to do something. A call to the built-in print writes to the screen: it takes any number of arguments, separates them by spaces, and follows them by a newline unless told otherwise.

print("Algorithm")
print("terminated.")

# several arguments, and a different ending
print("Algorithm", "terminated", end=".\n")

Writing an f before the opening quote of a string makes it an f-string, in which any expression placed in braces is evaluated and its value substituted:

n = 7
print(f"{n} squared is {n * n}")   # 7 squared is 49

In mathematics the legality of an operation depends on what it is applied to: addition is defined for numbers, composition for maps. In Python each value is an object, and every object has a type, which fixes the operations allowed on it. The interpreter uses types to enforce static semantics.

Types divide into scalar, meaning indivisible, and non-scalar, meaning having internal structure. Python has four primitive scalar types.

  1. int, the integers, written as usual: 5, -12.
  2. float, an approximation to the reals, written with a decimal point (3.0, -28.72) or in scientific notation (1.6e-19). Memory is finite and the reals are not, so a float holds one of finitely many values and float arithmetic is not the arithmetic of R\mathbb{R}: the expression 0.1 + 0.2 == 0.3 evaluates to False.
  3. bool, inhabited by exactly two values, True and False.
  4. None, inhabited by one value, used for the absence of a result.

Expressions and Relational Operators

Objects combined with operators according to the syntax form an expression, and every expression evaluates to a single object of some type. The built-in type reports which type an object has:

type(5)     # int
type(5.0)   # float

Control flow needs tests that come out True or False, and these are built with the relational operators <, <=, >, >=, == and !=. Every one of them evaluates to a bool.

Python’s notation differs from the pseudocode here. Mathematical assignment g=defeg \defeq e is written in Python with a single equals sign, g = e. Testing whether two expressions have the same value is ==, and testing that they do not is !=.

3.0 + 2.0   # the float 5.0
3 != 2      # the bool True

Problem 3.3.

Give the value of type(4 == 4) and of type(4.0). Then say what happens if the test in step 2 of Heron’s method is written with = in place of ==, and which of the three kinds of rule of the previous section it breaks.

Arithmetic Operators

Addition, subtraction and multiplication are +, - and *, and exponentiation is **. Division splits into three operators, two of which are the floor and the remainder of the last lesson.

  1. / always evaluates to a float: 5 / 3 gives 1.6666666666666667.
  2. // is floor division, the largest integer not exceeding the quotient. So 5 // 3 is 1 and -4 // 3 is -2.
  3. % is the remainder: 5 % 3 is 2. A modern processor carries out % and // in a few clock cycles, so we count each of them as a single elementary operation.

Remark (Floors and remainders in Python).

For integers a and b > 0, a // b is ⌊a/b⌋\lfloor a/b \rfloor and a % b is a mod ba \bmod b as defined in Definition 2.19. The identity z=b⌊z/b⌋+(z mod b)z = b\lfloor z/b\rfloor + (z \bmod b) therefore reads

a == b * (a // b) + a % b

and holds for every integer a. It holds for negative a because // rounds down, not towards zero.

Problem 3.4.

Verify that a % b and a - (a // b) * b are equal, first for b=3b = 3 and a=7,6,0,−1,−4a = 7, 6, 0, -1, -4, and then in general.

Suppose integer division were defined by truncation towards zero instead, so that −4-4 divided by 33 gave −1-1. What must the remainder then be for the identity to survive, and what happens to the guarantee 0⩽a mod b<b0 \leqslant a \bmod b < b?

Compound expressions are read by precedence: ** binds most tightly, then *, /, // and %, then + and -.

print(2 + 3 * 4)   # 14, not 20
print(5 + 4 % 3)   # 6, not 0   (% binds as tightly as * and /)
print(2 ** 3 * 4)  # 32, not 4096

Operators of equal precedence are read by associativity. Most associate to the left; exponentiation associates to the right.

print(5 - 4 - 3)    # -2, not 4
print(4 ** 3 ** 2)  # 4 ** 9 = 262144, not 64 ** 2 = 4096
print((4 ** 3) ** 2)  # 4096

Logical Operators

The three logical connectives, negation, conjunction and disjunction, are implemented as the keywords not, and and or. They act on bool values and are fixed by these tables, in which T and F abbreviate True and False.

anot a
TF
FT
aba and ba or b
TTTT
TFFT
FTFT
FFFF

So a and b holds exactly when both do, a or b when at least one does, and not a reverses the value. All three are reserved words: they are part of the syntax and cannot be reassigned or used as names.

Both and and or stop as soon as the answer is settled: in a and b the expression b is never evaluated when a is False, and in a or b it is never evaluated when a is True.

(3 != 2) and (5 % 3 == 0)   # False, since 5 % 3 is 2

The Operators in One Place

CategoryOperators
Arithmetic+, -, *, /, //, **, %, unary -, unary +
Relational<, <=, >, >=, ==, !=
Assignment=, +=, -=, *=, /=, //=, **=, %=, <<=, >>=
Logicaland, or, not

The compound assignments abbreviate an update in place: total += x means total = total + x.

The bitwise operators (<<, >>, &, |, ^, ~, &=, |=, ^=), and with them <<= and >>=, are not used in this lesson.

Variables and State

An equation such as y=x2y = x^2 ties yy to xx permanently: change xx and yy changes with it. Assignment does not. It creates a binding, at one moment in time, between a name and one object in memory, and the binding stands until it is replaced.

pi = 3.14159
radius = 11.0
area = pi * (radius ** 2)
radius = 14.0

The third statement evaluates pi * (radius ** 2) to the single float 380.13239 and binds area to it. The fourth rebinds radius, and area is untouched: it is bound to a number, not to a formula. This lets Heron’s method overwrite its guess gg at every step while the input xx stays fixed.

Names and Comments

A program has to be checked by a reader, and a correct program with opaque names is hard to check. The # symbol begins a comment, which the interpreter ignores.

a = 3.14159
b = 11.2
c = a * (b ** 2)

pi = 3.14159
diameter = 11.2
area = pi * ((diameter / 2.0) ** 2)

The two blocks compute different numbers, and only the second makes it visible which one is the area of a circle: the first squares a diameter where it should square a radius, and its names hide the fact.

Multiple Assignment

Several names may be bound at once. Every expression on the right of the = is evaluated in full before any name on the left is rebound, so the two sides do not interfere.

x, y = 2, 3
x, y = y, x

print("x is", x)   # x is 3
print("y is", y)   # y is 2

Exchanging two values therefore needs no third name to hold one of them.

Problem 3.5.

Start from a, b = 1, 1 and perform a, b = b, a + b three times. Give the values of a and b after each of the three steps, and name the sequence they run through. Then say what a, b = b, a + b would produce if the right-hand side were evaluated one name at a time, left to right.

Numbers in Other Bases

The string 28122812 is a representation of a number rather than the number itself. Each digit occupies a position whose value is a power of ten:

2812=2⋅103+8⋅102+1⋅101+2⋅100.2812 = 2 \cdot 10^3 + 8 \cdot 10^2 + 1 \cdot 10^1 + 2 \cdot 10^0 .

The rightmost digit sits in the units place, 100=110^0 = 1, and each position to its left is worth ten times the one before, so the leftmost 22 of 28122812 stands for two thousand while the rightmost stands for two. This is a positional numeral system: what a digit means depends on where it sits.

Base ten comes from counting on fingers, not from mathematics. Ten is divisible by only four numbers, 1,2,51, 2, 5 and 1010, which limits how easily fractions can be handled; twelve, divisible by 1,2,3,4,61, 2, 3, 4, 6 and 1212, would serve better. A digital circuit holds two voltage levels, high and low, so machines work in base two, where arithmetic is carried out directly by sequences of logic gates.

That every natural number has exactly one representation of this shape in any base b>1b > 1 is Theorem 2.21 of the last lesson, and the notation is fixed by its corollary. Read as a statement about digit strings, it says that the base-bb expansion of n∈N0n \in \mathbb{N}_0 is the string drdr−1⋯d0d_r d_{r-1} \cdots d_0 satisfying three conditions:

  1. n=drbr+dr−1br−1+⋯+d0b0n = d_r b^r + d_{r-1}b^{r-1} + \cdots + d_0 b^0;
  2. every digit satisfies 0⩽di<b0 \leqslant d_i < b;
  3. if n>0n > 0 then the leading digit drd_r is not 00, and the expansion of 00 is the string 00 in every base.

The first condition says that the digits record how many copies of each power of bb are wanted; in decimal the places from the right are units, tens, hundreds, and in binary they are 1,2,4,8,16,…1, 2, 4, 8, 16, \ldots. The second restricts the available digits to 0,1,…,b−10, 1, \ldots, b-1, and without it uniqueness fails: if X\text{X} were a decimal digit standing for ten, then X2\text{X}2 and 102102 would both represent one hundred and two. The third bans leading zeros, without which 0142301423 and 14231423 would be different strings for one number.

Following the corollary we write

(drdr−1⋯d0)b=drbr+dr−1br−1+⋯+d0b0(d_r d_{r-1} \cdots d_0)_b = d_r b^r + d_{r-1} b^{r-1} + \cdots + d_0 b^0

for the number with this expansion, and a string carrying no subscript is decimal.

Remark (Names of the small bases).

The base-22, 33, 88, 1010 and 1616 expansions are called binary, ternary, octal, decimal and hexadecimal. Bases past ten need more than the ten digits, and the convention is to carry on with letters: A=10\text{A} = 10, B=11\text{B} = 11, and so on up to Z=35\text{Z} = 35.

Example 3.6 (One number in six bases).

The decimal expansion of 10231023 is 10231023 itself. Since 1023=210−11023 = 2^{10} - 1, every power of two from 202^0 to 292^9 occurs exactly once and the binary expansion is ten ones. For base thirty-six, 1023=28⋅36+151023 = 28 \cdot 36 + 15, and 2828 is S\text{S} while 1515 is F\text{F}. Altogether

1023=(1111111111)2=(1101220)3=(1777)8=(1023)10=(3FF)16=(SF)36.1023 = (1111111111)_2 = (1101220)_3 = (1777)_8 = (1023)_{10} = (3\text{FF})_{16} = (\text{SF})_{36} .

Python writes the three bases a machine uses with bin, oct and hex, each returning a string carrying a prefix that names the base:

n = 1023

bin(n)   # '0b1111111111'
oct(n)   # '0o1777'
hex(n)   # '0x3ff'

The same prefixes may be typed directly as literals, so the conversions can be checked against each other:

a = 0b1111111111
b = 0o1777
c = 0x3FF

a == b == c == 1023   # True

For any other base, int takes the base as a second argument and reads a string written in it:

int('1101220', 3)   # 1023
int('sf', 36)       # 1023

Problem 3.6.

Find the binary, ternary, octal, hexadecimal and base-3636 expansions of 17291729 by hand, using A\text{A} to F\text{F} as the extra hexadecimal digits and A\text{A} to Z\text{Z} for base thirty-six. Check each against int, and against bin, oct and hex.

Problem 3.7.

A number has octal expansion (2745)8(2745)_8. Give its decimal value and its hexadecimal expansion.

Problem 3.8.

Let b>1b > 1. Write down, in terms of bb, the smallest and the largest number whose base-bb expansion has exactly three digits.

Branching

Everything written so far is a straight-line program: the statements run in the order they appear, each exactly once. Such a program is easy to reason about, and limited. If one atomic operation takes one unit of time, a straight-line program of NN lines takes at most NN units however large its input, because no line ever runs twice.

The first way to let a program do more is branching: a test is evaluated, and the block of statements that runs next depends on the answer.

Conditional Statements

The basic branching construct is if–else. It consists of a test evaluating to True or False, a block run when the test is True, and an optional else block run when it is False.

Python marks the extent of a block by indentation. Where other languages use braces or an end keyword, Python uses the whitespace itself, so the layout of a program shows its structure.

x = 14

if x % 2 == 0:
    print("The integer is even.")
else:
    print("The integer is odd.")

print("Branching complete.")

The test uses x % 2, the remainder on division by two, which is the last digit of the binary expansion of x. It is 0 for even x and 1 for odd. The final print is not indented, so it belongs to neither block and runs either way; indenting it by four spaces would make it part of the else and suppress it for even x.

Because indentation carries meaning, a long expression cannot be broken across lines anywhere. A backslash at the end of a line continues it explicitly, and a line inside unclosed brackets continues implicitly.

# explicit continuation
alpha = 1.61803398875 + \
        2.71828182845 + \
        3.14159265359

# implicit continuation, inside parentheses
beta = (1.61803398875 +
        2.71828182845 +
        3.14159265359)

Nested Conditionals

A block inside a branch may itself branch, which builds a decision tree.

Where the cases are mutually exclusive, a chain of else blocks each containing one if grows an extra level of indentation per case. The keyword elif collapses the chain: its test is evaluated exactly when every test above it has come out False.

n = 15

if n % 2 == 0:
    if n % 3 == 0:
        print("n is a multiple of 6.")
    else:
        print("n is even, but not divisible by 3.")
elif n % 3 == 0:
    print("n is odd and divisible by 3.")
else:
    print("n is not divisible by 2 or 3.")

Compound Tests and Updating State

Tests may be combined with and, or and not. How they are combined changes how much work the program does.

Take the problem of returning the largest positive number among aa, bb and cc, or, if none of them is positive, the smallest of the three. Each of the three is positive or not, so there are 23=82^3 = 8 cases, and a program with one branch per case tests the same three comparisons over and over.

Binding a provisional answer and updating it only when a later value beats it replaces the eight cases by three independent tests:

a, b, c = -5, 12, 7

# if none is positive the answer is the smallest, so start there
target = min(a, b, c)

if a > 0:
    target = a
if b > 0 and b > target:
    target = b
if c > 0 and c > target:
    target = c

print(target)   # 12

The built-in min returns the smallest of its arguments, and max the largest. Each of the three variables has its sign examined once.

Conditional Expressions

For a choice between two values rather than between two blocks of work, Python supplies the conditional expression, which packs an if–else into a single expression:

expression_if_true if test else expression_if_false

A variable may therefore be bound according to a test without a multi-line block. The absolute value of Definition 1.65 is given by cases, so there are several ways to compute it. The most direct is a standard if block:

x = -10

if x < 0:
    x = -x

For a body consisting of a single short statement, Python permits writing the block on the same line as the if:

if x < 0: x = -x   # only for very short statements

Branching can also be avoided entirely by using the fact that True and False behave as 11 and 00 in arithmetic:

y = (x < 0) * (-x) + (x >= 0) * x   # works, but hard to read

This works, but it is hard to read, it evaluates both branches every time, and it hides the case split inside a multiplication. The conditional expression is clearer:

y = x if x >= 0 else -x

Python also provides the built-in abs for this purpose, so abs(-10) returns 10.

Problem 3.9.

Using conditional expressions and no if statements, write single-line definitions of

  1. the sign of xx, which is −1-1, 00 or 11 according as xx is negative, zero or positive;
  2. the larger of xx and yy, without using max;
  3. the distance ∣x−y∣|x - y| between two reals, without using abs.

Constant Time

Branching does not remove the limit on straight-line programs. Each block is entered at most once on a run, so if one atomic operation takes one unit of time, a branching program of NN lines can never exceed NN units, and its maximum running time is hard-bounded by a constant k⩽Nk \leqslant N.

Definition 3.7 (Constant time).

An algorithm runs in constant time if there is a constant kk, depending on the algorithm alone, such that the algorithm performs at most kk atomic operations on every input, whatever the size or magnitude of that input.

Every straight-line program runs in constant time, and so does every branching program, with kk the number of lines.

Remark (Beyond constant time).

Constant time is a strong restriction. Consider computing the factorial of an integer nn. It requires n−1n - 1 multiplications, and nn is not bounded, so no fixed number of multiplication statements serves for every nn; a program with one hard-coded branch per value of nn would need infinitely many branches and would violate the finiteness in the definition of an algorithm. The same holds for reading the base-bb digits of a number, whose count grows like log⁡bn\log_b n. Computations whose length grows with the input need control flow that can return to an earlier instruction.

Problem 3.10.

Let a, b and c be the coefficients of ax2+bx+cax^2 + bx + c. Write a program that computes the discriminant Δ=b2−4ac\Delta = b^2 - 4ac and reports whether the polynomial has two distinct real roots, one repeated real root, or none. Treat a=0a = 0 separately, where the expression is linear rather than quadratic, and say what your program should report when a=b=0a = b = 0.

Problem 3.11.

Consider the rule which replaces an integer nn by 3n+13n + 1 when nn is odd and by n/2n/2 when nn is even. Using conditional expressions, write a program that applies the rule three times in succession to a given positive integer. Check that starting from n=7n = 7 it produces 3434, and find a starting value below 1010 from which three applications return the starting value itself.

Iteration

Branching programs are bound by constant time: each instruction is executed at most once, so the whole computation is capped by the static length of the source. The base expansions of the first chapter show the limit. Every step applied the same pair of operations, // and %, to the current quotient, and with no way of repeating those operations automatically we performed each step by hand. Computing the factorial of nn, testing whether a number is prime, extracting the digits of an arbitrarily large base expansion: these tasks take longer on larger inputs, and no branching program can perform them.

Iteration sends execution back to an instruction already passed.

Definition 3.8 (Iteration).

An iteration, or loop, is a control structure that sends execution back to an instruction it has already passed, so that a block of statements runs repeatedly. How many times it runs is decided by the state of the computation while it runs, not by the length of the program.

testnext statementloop bodytruefalse
Figure 3.2. The shape of a loop. The test is evaluated, the body runs when it holds, and control returns to the test; the loop is left by the other exit.

The While Loop

Python’s while statement has the syntax of if: a test, a colon, and an indented body. The test is evaluated; if it is True the body runs in full and control returns to the test; if it is False the body is skipped and execution continues after the loop.

The following program computes x2x^2 by adding xx to a running total xx times.

x = 3
ans = 0
num_iterations = 0

while num_iterations != x:
    ans = ans + x
    num_iterations = num_iterations + 1

print(f"{x} squared is {ans}")

Recording the values of the variables each time the test is reached, as though one were the interpreter, is called hand simulation, and it is how we check what a loop does.

Test evaluationxansnum_iterationsTest
1st300True
2nd331True
3rd362True
4th393False

On the fourth evaluation num_iterations has reached x, and the program prints 3 squared is 9.

Whether it stops at all depends on x, and there are three cases. If x=0x = 0, the test fails at once, the body never runs, and 0 squared is 0 is printed. If x>0x > 0, the counter starts below x and rises by exactly one per pass, so after xx passes it equals x and the loop ends with the right answer. If x<0x < 0, the counter runs through 0,1,2,…0, 1, 2, \ldots and never equals a negative number: the test is never False, and the program runs forever. This is an infinite loop.

Weakening the test to num_iterations < abs(x) stops the loop after abs(x) passes, but each pass still adds the negative number x, so the program would announce that (−3)2=−9(-3)^2 = -9. The body has to be corrected too:

x = -3
ans = 0
num_iterations = 0

while num_iterations < abs(x):
    ans = ans + abs(x)
    num_iterations = num_iterations + 1

print(f"{x} squared is {ans}")   # -3 squared is 9

Example 3.9 (The leading digit).

Floor division by ten discards the last decimal digit, so applying it until one digit is left leaves the first digit. How many times it must be applied is not known before the loop starts, which is what a while loop is for.

n = 72658489290098
n = abs(n)

while n >= 10:
    n = n // 10

print(n)   # 7

Two further pieces of Python are needed for the problems below. Writing + between two strings joins them end to end, so 'X' + 'X' is 'XX'. And input(prompt) prints its prompt, waits for the user to type a line, and returns what was typed as a string; int converts a string of digits to the integer it denotes, so int(input('n? ')) reads a number.

Problem 3.12.

The program below should print the letter X a given number of times. Replace the comment by a while loop that appends 'X' to to_print exactly num_x times, and say what your loop does when the user enters 00 or a negative number.

num_x = int(input('How many times should I print the letter X? '))
to_print = ''
# append X to to_print num_x times
print(to_print)

Leaving a Loop Early

A break statement ends the loop containing it at once and passes control to the first statement after it, without returning to the test.

# the smallest positive integer divisible by both 11 and 12
x = 1

while True:
    if x % 11 == 0 and x % 12 == 0:
        break
    x = x + 1

print(x, 'is divisible by 11 and 12')   # 132 is divisible by 11 and 12

The test while True never fails, so the break is the only way out, and the proof that the loop stops is about the break. This arrangement suits a loop whose exit condition is natural to check partway through the body rather than at the top.

Where one loop sits inside another, a break ends only the loop that immediately contains it; the outer loop carries on.

Problem 3.13.

Write a program that reads ten integers, one at a time, and then prints the largest odd number among them, or a message saying that none was odd. Do not use max.

The For Loop

The loops above share a pattern: a counter is set up, tested at the top, and advanced at the bottom of the body. The for statement does that bookkeeping itself. Its form is

for variable in sequence:
    body

The variable is bound to the first entry of the sequence and the body runs; then to the second, and the body runs again; and so on until the sequence is exhausted or a break intervenes.

total = 0

for num in (77, 11, 3):
    total = total + num

print(total)   # 91

The object (77, 11, 3) is a tuple, an ordered finite sequence written in parentheses.

The Range Function

The sequence is most often produced by range, which generates a progression of integers and takes one, two or three arguments.

With one argument, range(stop) gives 0,1,…,stop−10, 1, \ldots, \text{stop} - 1.

for i in range(4):
    print(i)     # 0, then 1, then 2, then 3

With two, range(start, stop) gives start,start+1,…,stop−1\text{start}, \text{start}+1, \ldots, \text{stop}-1. The lower end is included and the upper end is not.

total = 0

for x in range(5, 11):
    total = total + x

print(total == 5 + 6 + 7 + 8 + 9 + 10)   # True

With three, range(start, stop, step) gives start,start+step,start+2 step,…\text{start}, \text{start} + \text{step}, \text{start} + 2\,\text{step}, \ldots, and stops before reaching stop. For positive step the last entry is the largest start+i step\text{start} + i\,\text{step} below stop; a negative step descends instead, so range(40, 5, -10) gives 40,30,20,1040, 30, 20, 10.

total = 0

for x in range(10, 3, -1):
    if x % 2 == 1:
        total = total + x

print(total)   # 9 + 7 + 5 = 21

Choosing the step to land on the right numbers is often more trouble than testing them inside the body. The following sums the odd numbers between m and n whatever the parity of m:

m, n = 4, 10
total = 0

for x in range(m, n + 1):
    if x % 2 == 1:
        total = total + x

print(total)   # 5 + 7 + 9 = 21

The two-argument form covers the one-argument form: range(0, 3) produces the same sequence as range(3). The entries are produced one at a time as the loop asks for them rather than stored all at once, so range(1000000) costs no more memory than range(3).

Remark (Which loop to use).

A for loop is the right choice when the number of passes is settled before the loop begins, since range then states that number and no counter can be mismanaged. A while loop is the right choice when it is not: the leading-digit program above stops when the number falls below ten, and how many divisions that takes is a fact about the input rather than about the program.

Example 3.10 (Squaring by repeated addition again).

The squaring program loses its counter entirely when written with for:

x = -3
ans = 0

for num_iterations in range(abs(x)):
    ans = ans + abs(x)

print(f"{x} squared is {ans}")   # -3 squared is 9

There is no explicit test and no explicit increment; range supplies both.

Reassigning the Loop Variable

Assigning to the loop variable inside the body does not disturb the loop.

for i in range(2):
    print(i)
    i = 0
    print(i)

This prints 0, 0, 1, 0 and stops. The sequence is fixed when the for statement is first reached, and at the start of each pass the variable is rebound to the next entry of it, whatever happened to the variable in between. The loop is equivalent to

index = 0
last_index = 1

while index <= last_index:
    i = index
    print(i)
    i = 0
    print(i)
    index = index + 1

For the same reason, changing a variable that was used in the range call has no effect, because the call is evaluated once:

x = 1

for i in range(x):
    print(i)
    x = 4

# prints 0, and nothing else

An inner for is a different matter: its range call is reached afresh on every pass of the outer loop and so is evaluated again.

x = 3

for j in range(x):
    print('Outer')
    for i in range(x):
        print('  Inner')
        x = 2

The outer range(x) is evaluated once, with x=3x = 3, so the outer loop makes three passes. The inner range(x) sees x=3x = 3 on the first pass and x=2x = 2 afterwards, giving 3+2+2=73 + 2 + 2 = 7 inner passes in total.

Iterating Over a String

A string is a sequence of characters. Its positions are numbered from 00, so a string of length kk occupies positions 0,1,…,k−10, 1, \ldots, k-1: len(s) gives the length, s[i] gives the character at position i, and the slice s[i:j] gives the characters at positions i up to but not including j. That upper end is excluded for the same reason it is excluded from range, and the two conventions agree: s[0:len(s)] is the whole of s, and the positions it covers are exactly those produced by range(len(s)).

Combined with in, the for statement walks a string directly, binding the variable to one character at a time and dispensing with the positions altogether.

total = 0

for c in '12345678':
    total = total + int(c)

print(total)   # 36

Problem 3.14.

Write a program that computes 5+6+⋯+1005 + 6 + \cdots + 100 with a for loop, and check the result against the closed form for an arithmetic progression obtained in the last lesson. Then do the same for 5+7+9+⋯+995 + 7 + 9 + \cdots + 99, once by choosing the step of the range and once by testing inside the body.

Nested Loops

A loop inside a loop runs the inner loop to completion on every pass of the outer one.

Example 3.11 (Patterns).

An n×nn \times n block of asterisks needs one loop for the rows and one for the columns:

n = 5

for row in range(n):
    for col in range(n):
        print('*', end='')
    print()
*****
*****
*****
*****
*****

The bare print() after the inner loop ends the line. Letting the inner range depend on the outer variable changes the shape:

n = 5

for row in range(n):
    print(row, end=' ')
    for col in range(row):
        print('*', end=' ')
    print()
0
1 *
2 * *
3 * * *
4 * * * *

Row kk carries kk asterisks, so the block becomes a triangle.

Continue and Pass

Two keywords sit alongside break. A continue abandons the rest of the current pass and goes straight to the next one: back to the test for a while, on to the next entry for a for. A pass does nothing, and exists because Python’s syntax requires a statement in places where no action is wanted.

for n in range(200):
    if n % 3 == 0:
        continue
    elif n == 8:
        break
    else:
        pass
    print(n, end=' ')

# 1 2 4 5 7

The multiples of three never reach the print, the loop ends when n reaches 88, and the remaining values pass through the else branch and are printed.

Divisibility and Primality

Definition 3.12 (Divisibility).

Let d,n∈Zd, n \in \mathbb{Z}. We say dd divides nn, written d∣nd \mid n, if n=dqn = dq for some q∈Zq \in \mathbb{Z}; equivalently, for d≠0d \neq 0, if n mod d=0n \bmod d = 0. In Python this is the test n % d == 0.

Definition 3.13 (Prime and composite).

An integer n⩾2n \geqslant 2 is prime if its only positive divisors are 11 and nn, and composite otherwise. The integers 00 and 11 and the negative integers are neither.

Checking that 9191 is composite takes a single operation once the divisor 77 is known, since 91 % 7 == 0 settles it; finding the divisor is where the work lies. Read as an instruction, the definition says to test every candidate between 22 and n−1n - 1 and to stop at the first one that divides.

n = 91
is_prime = n >= 2

for factor in range(2, n):
    if n % factor == 0:
        is_prime = False
        break

if is_prime:
    print(f'{n} is prime')
else:
    print(f'{n} is not prime')

# 91 is not prime

One divisor settles the question, so the loop stops at the first one it finds. Wrapping the whole thing in a second loop lists the primes below a bound:

for n in range(2, 100):
    is_prime = True
    for factor in range(2, n):
        if n % factor == 0:
            is_prime = False
            break
    if is_prime:
        print(n, end=' ')

# 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Trial Division to the Square Root

Testing every candidate up to n−1n - 1 is more than necessary.

Proposition 3.14 (A composite number has a small divisor).

Let n⩾2n \geqslant 2. Then nn is composite if and only if some integer dd with 2⩽d⩽n2 \leqslant d \leqslant \sqrt{n} divides nn.

Discussion.

Divisors come in pairs: if d∣nd \mid n then dd and n/dn/d are both divisors and their product is nn. A product of two numbers both exceeding n\sqrt{n} exceeds nn, so the two members of a pair cannot both lie above n\sqrt{n}, and one of them is at or below it. The proof takes the least divisor above 11; its partner is then the larger of the two, and the inequality d⩽n/dd \leqslant n/d can be squared. The converse direction is immediate: a divisor in that range is neither 11 nor nn, because n<n\sqrt{n} < n once n⩾2n \geqslant 2.

Proof.

Suppose nn is composite. The set D={ d∈Z:d⩾2 and d∣n }D = \{\, d \in \mathbb{Z} : d \geqslant 2 \text{ and } d \mid n \,\} contains nn, so it is non-empty; let dd be its least element and write n=dqn = dq with q∈Zq \in \mathbb{Z}, so that q⩾1q \geqslant 1 and q∣nq \mid n.

We first rule out q=1q = 1. If q=1q = 1 then d=nd = n. But nn is composite, so it has a divisor ee with 1<e<n1 < e < n; that ee lies in DD and is smaller than n=dn = d, contradicting the minimality of dd. Hence q⩾2q \geqslant 2, so q∈Dq \in D and therefore q⩾dq \geqslant d. Consequently

d2⩽dq=n,d^2 \leqslant dq = n ,

so d⩽nd \leqslant \sqrt{n}.

Conversely, suppose 2⩽d⩽n2 \leqslant d \leqslant \sqrt{n} and d∣nd \mid n. From n⩾2n \geqslant 2 we get n<n\sqrt{n} < n, so d≠nd \neq n, and d≠1d \neq 1 by hypothesis. Thus nn has a positive divisor other than 11 and nn, so it is composite.

Only the integers up to n\sqrt{n} need be tested, and 22 can be dealt with separately so that the loop skips the even candidates:

n = 1010809
is_prime = n >= 2

if n > 2 and n % 2 == 0:
    is_prime = False
else:
    max_factor = round(n ** 0.5)
    for factor in range(3, max_factor + 1, 2):
        if n % factor == 0:
            is_prime = False
            break

print(f'{n} is prime: {is_prime}')   # 1010809 is prime: True

round(n ** 0.5) returns either ⌊n⌋\lfloor \sqrt{n} \rfloor or one more, never less, so the loop may test one candidate too many and never one too few.

The first program tests up to n−2n - 2 candidates and the second about n/2\sqrt{n}/2: for n=1010809n = 1010809, about a million tests against about five hundred. Python’s time module measures it. The statement import time makes the module’s tools available, and time.time() returns the current time in seconds. A colon inside the braces of an f-string says how to format the value: :.2f prints it with two digits after the decimal point.

import time

n = 1010809

time0 = time.time()
is_prime_basic = True
for factor in range(2, n):
    if n % factor == 0:
        is_prime_basic = False
        break
time1 = time.time()
print(f'Basic: {(time1 - time0) * 1000:.2f} ms')

time0 = time.time()
is_prime_fast = n >= 2
if n > 2 and n % 2 == 0:
    is_prime_fast = False
else:
    max_factor = round(n ** 0.5)
    for factor in range(3, max_factor + 1, 2):
        if n % factor == 0:
            is_prime_fast = False
            break
time1 = time.time()
print(f'Optimised: {(time1 - time0) * 1000:.2f} ms')

print(is_prime_basic == is_prime_fast)   # True

The two agree, and on an ordinary machine the first takes tens of milliseconds where the second takes a small fraction of one.

Searching for the nnth Object

Finding the nnth number with a given property is a different kind of problem: the answer is not known in advance, so there is no range to run over. A counter of successes and a candidate that advances by one at a time turn it into a while loop.

target = 5
found = 0
guess = 0

while found <= target:
    guess = guess + 1
    if guess % 4 == 0 or guess % 7 == 0:
        found = found + 1

print(f'Counting from zero, entry {target} of the multiples of 4 or 7 is {guess}')
# entry 5 is 16

The multiples of four or seven begin 4,7,8,12,14,164, 7, 8, 12, 14, 16, and counting from zero the entry numbered 55 is 1616. Substituting the primality test for the divisibility test finds the nnth prime:

target = 10
found = 0
guess = 1

while found <= target:
    guess = guess + 1
    is_prime = guess >= 2
    if guess > 2 and guess % 2 == 0:
        is_prime = False
    else:
        max_factor = round(guess ** 0.5)
        for factor in range(3, max_factor + 1, 2):
            if guess % factor == 0:
                is_prime = False
                break
    if is_prime:
        found = found + 1

print(f'Counting from zero, prime {target} is {guess}')   # prime 10 is 31

The primes are 2,3,5,7,11,13,17,19,23,29,312, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, and counting from zero the one numbered 1010 is 3131.

Problem 3.15.

Write a program that prints the sum of the primes strictly between 22 and 10001000, using a primality test nested inside a loop over the odd integers from 33 to 999999. Then say how many candidate divisors your program tests in total, and how many the version without the square-root bound would test.

Digits in an Arbitrary Base

In the first chapter we read off base-bb expansions by hand, dividing by bb, recording the remainder as the next digit from the right, and continuing with the quotient until it reached zero. Automating that needs a way of repeating instructions until a condition is met, which the while loop provides. The number of repetitions is the number of digits, and it is not known before the divisions are done.

Proposition 3.15 (Digit extraction).

Let b>1b > 1 and n∈Nn \in \mathbb{N}. Define n0=nn_0 = n and nk+1=⌊nk/b⌋n_{k+1} = \lfloor n_k / b \rfloor. Then nk=0n_k = 0 for some kk, the least such kk is m=⌊log⁡bn⌋+1m = \lfloor \log_b n \rfloor + 1, and for 0⩽i<m0 \leqslant i < m

ni mod b=zi,n_i \bmod b = z_i ,

where zm−1⋯z1z0z_{m-1} \cdots z_1 z_0 is the base-bb expansion of nn.

Discussion.

One pass of the loop performs the split z=b⌊z/b⌋+(z mod b)z = b\lfloor z/b \rfloor + (z \bmod b), which removes the last digit. The proof identifies nkn_k explicitly, as the number whose digits are the top m−km - k digits of nn, and proves this by induction on kk. The step is the split applied to nkn_k, and the remainder is the digit zkz_k by the uniqueness of quotient and remainder, as in the uniqueness half of Theorem 2.21. The number of steps then follows: the sum defining nkn_k is empty exactly when k=mk = m, and is at least 11 before that, because its leading digit is not zero.

Proof.

By Corollary 2.22 the number nn has a unique expansion

n=∑i=0m−1zibi,zi∈{0,…,b−1},zm−1≠0,m=⌊log⁡bn⌋+1.n = \sum_{i=0}^{m-1} z_i b^i, \qquad z_i \in \{0, \ldots, b-1\}, \quad z_{m-1} \neq 0, \quad m = \lfloor \log_b n \rfloor + 1 .

We claim that

nk=∑i=km−1zib i−k(0⩽k⩽m).n_k = \sum_{i=k}^{m-1} z_i b^{\,i-k} \qquad (0 \leqslant k \leqslant m).

For k=0k = 0 this is the expansion itself. Assume it for some k<mk < m and split off the term i=ki = k:

nk=zk+b∑i=k+1m−1zib i−k−1.n_k = z_k + b\sum_{i=k+1}^{m-1} z_i b^{\,i-k-1} .

The sum on the right is an integer and 0⩽zk<b0 \leqslant z_k < b, so this is the division of nkn_k by bb with quotient and remainder, and by the uniqueness of that division

nk+1=⌊nkb⌋=∑i=k+1m−1zib i−k−1,nk mod b=zk,n_{k+1} = \left\lfloor \frac{n_k}{b} \right\rfloor = \sum_{i=k+1}^{m-1} z_i b^{\,i-k-1}, \qquad n_k \bmod b = z_k ,

which is the claim at k+1k+1 together with the stated identity for the digits.

At k=mk = m the sum is empty, so nm=0n_m = 0. For k<mk < m every term is non-negative and the term i=m−1i = m-1 is zm−1b m−1−k⩾1z_{m-1}b^{\,m-1-k} \geqslant 1, so nk⩾1n_k \geqslant 1. Hence mm is the least index at which the sequence vanishes.

The loop below carries out the proposition. Digits are produced from the last to the first, so each new one is put in front of what has been built so far, and digits above nine are turned into letters: str(d) turns the number d into the string of its decimal digits, ord(c) gives the code number of the character c, chr(k) gives the character with code k, and the codes of A to Z run consecutively.

n = 12345
b = 17
digits = ''

while n > 0:
    remainder = n % b
    if remainder < 10:
        digits = str(remainder) + digits
    else:
        digits = chr(ord('A') + remainder - 10) + digits
    n = n // b

print(digits)   # 28C3

Example 3.16 (Checking the expansion).

Reading (28C3)17(28\text{C}3)_{17} back gives

2⋅173+8⋅172+12⋅17+3=9826+2312+204+3=12345,2 \cdot 17^3 + 8 \cdot 17^2 + 12 \cdot 17 + 3 = 9826 + 2312 + 204 + 3 = 12345 ,

as the proposition says.

Problem 3.16.

The loop above prints nothing when n=0n = 0. Say why, in terms of the hypotheses of the proposition, and repair it so that it prints '0'. Then use it to find the base-77 and hexadecimal expansions of 99999999, and check them against the by-hand method of the first chapter.

Problem 3.17.

Write a program that repeatedly asks the user for a string and prints it back, stopping when the user enters 'done'. It should then print 'Bye!' followed by the number of strings entered, not counting 'done'.

Running Time

Timing the two primality tests measured one machine at one time. The number of instructions a program performs depends on the compiler and on the hardware, and we do not in any case know it exactly. We want a statement about the algorithm instead: how the amount of work grows with the input.

Definition 3.17 (Elementary operation and running time).

An elementary operation is one the machine performs in a bounded number of clock cycles independently of the values involved: an arithmetic operation or a comparison on numbers of bounded size, a read or a write of one stored value. Both a // b and a % b are elementary in this sense.

The running time of an algorithm is the number of elementary operations it performs, counted as a function of its input.

Two things remain to be fixed: how the size of an input is measured, and how precisely we count.

Landau Notation

Definition 3.18 (Landau's OO).

Let g:N→R⩾0g : \mathbb{N} \to \mathbb{R}_{\geqslant 0}. Then O(g)O(g) is the set of functions f:N→R⩾0f : \mathbb{N} \to \mathbb{R}_{\geqslant 0} for which there exist α∈R>0\alpha \in \mathbb{R}_{>0} and n0∈Nn_0 \in \mathbb{N} with

f(n)⩽α⋅g(n)for all n⩾n0.f(n) \leqslant \alpha \cdot g(n) \qquad \text{for all } n \geqslant n_0 .

The constant α\alpha must not depend on nn; were it allowed to, every function would lie in O(1)O(1) and the definition would say nothing. Nor does the inequality have to hold everywhere: it may fail for finitely many nn, so changing ff at the first million values leaves the statement untouched.

Instead of f∈O(g)f \in O(g) one usually writes

f=O(g),orf(x)=O(g(x)) as x→∞,f = O(g), \qquad\text{or}\qquad f(x) = O\bigl(g(x)\bigr) \text{ as } x \to \infty ,

read ”ff is big-Oh of gg”. The equals sign here is not the symmetric one, since O(g)O(g) is a set and ff is a member of it; the notation is standard nonetheless.

Example 3.19 (A cubic is O(x4)O(x^4)).

Take f(x)=4x3+7xf(x) = 4x^3 + 7x and g(x)=x4g(x) = x^4. For x⩾1x \geqslant 1 we have x3⩽x4x^3 \leqslant x^4 and x⩽x4x \leqslant x^4, so

4x3+7x⩽4x4+7x4=11x4,4x^3 + 7x \leqslant 4x^4 + 7x^4 = 11x^4 ,

and α=11\alpha = 11 with n0=1n_0 = 1 meets the definition. Hence 4x3+7x=O(x4)4x^3 + 7x = O(x^4).

Example 3.20 (A tight bound).

Take f(n)=3n2+5n+7f(n) = 3n^2 + 5n + 7 and g(n)=n2g(n) = n^2. For n⩾1n \geqslant 1 we have n⩽n2n \leqslant n^2 and 1⩽n21 \leqslant n^2, so

3n2+5n+7⩽3n2+5n2+7n2=15n2,3n^2 + 5n + 7 \leqslant 3n^2 + 5n^2 + 7n^2 = 15n^2 ,

and α=15\alpha = 15 with n0=1n_0 = 1 serves. Hence 3n2+5n+7=O(n2)3n^2 + 5n + 7 = O(n^2).

Here the bound grows at the same rate as ff itself, where the previous example bounded a cubic by a quartic. Both statements are true, but the cubic-by-quartic one loses information, since x4x^4 grows strictly faster than 4x3+7x4x^3 + 7x; the same argument with α=11\alpha = 11 gives the sharper 4x3+7x=O(x3)4x^3 + 7x = O(x^3). A function lies in O(g)O(g) for many different gg, and the useful statement uses the slowest-growing gg available.

Example 3.21 (When the definition fails).

Not every pair of functions is related this way: n2n^2 is not O(n)O(n). Suppose it were, so that n2⩽αnn^2 \leqslant \alpha n for some α>0\alpha > 0 and all n⩾n0n \geqslant n_0. Dividing by nn, which is positive, gives n⩽αn \leqslant \alpha for all n⩾n0n \geqslant n_0. But

n=max⁡(n0,⌈α⌉)+1n = \max\bigl(n_0, \lceil \alpha \rceil\bigr) + 1

satisfies n⩾n0n \geqslant n_0 and n>αn > \alpha, which contradicts it. So no constant serves, and the direction of an OO statement is not reversible: n=O(n2)n = O(n^2) holds while n2=O(n)n^2 = O(n) does not.

The argument of the first two examples works for any polynomial.

Proposition 3.22 (Polynomials).

Let f(n)=adnd+ad−1nd−1+⋯+a1n+a0f(n) = a_d n^d + a_{d-1}n^{d-1} + \cdots + a_1 n + a_0 with every ai⩾0a_i \geqslant 0 and d∈N0d \in \mathbb{N}_0. Then f=O(nd)f = O(n^d).

Discussion.

The proof uses one inequality: for n⩾1n \geqslant 1 and i⩽di \leqslant d we have ni⩽ndn^i \leqslant n^d, because raising a number at least 11 to a larger exponent cannot decrease it. Applying it to each term replaces every power by the top power, and what is left is the sum of the coefficients, which serves as the constant α=ad+⋯+a0\alpha = a_d + \cdots + a_0 of the definition. Non-negative coefficients let each term be bounded separately; with negative coefficients the same bound holds after taking absolute values of the coefficients.

Proof.

Let n⩾1n \geqslant 1. For each ii with 0⩽i⩽d0 \leqslant i \leqslant d we have ni⩽ndn^i \leqslant n^d, so aini⩽ainda_i n^i \leqslant a_i n^d since ai⩾0a_i \geqslant 0. Adding these d+1d+1 inequalities,

f(n)=∑i=0daini  ⩽  (∑i=0dai)nd.f(n) = \sum_{i=0}^{d} a_i n^i \;\leqslant\; \left(\sum_{i=0}^{d} a_i\right) n^d .

Taking α=∑i=0dai\alpha = \sum_{i=0}^{d} a_i, which is a positive constant unless every aia_i is 00, and n0=1n_0 = 1, the definition is satisfied.

Remark (The limit form).

For a reader who has met limits there is a shorter route to most OO statements. Suppose g(n)≠0g(n) \neq 0 from some point on and the ratio f(n)/g(n)f(n)/g(n) tends to a finite limit,

lim⁡n→∞f(n)g(n)=L<∞.\lim_{n \to \infty} \frac{f(n)}{g(n)} = L < \infty .

Then f=O(g)f = O(g): beyond some n0n_0 the ratio stays below L+1L + 1, so α=L+1\alpha = L + 1 meets the definition. The cubic example is settled in one line this way, since (4x3+7x)/x4→0(4x^3 + 7x)/x^4 \to 0, and so is the tight bound, since (3n2+5n+7)/n2→3(3n^2 + 5n + 7)/n^2 \to 3.

The converse fails only because the ratio need not converge at all, and replacing the limit by the limit superior repairs it: f=O(g)f = O(g) holds exactly when

lim sup⁡n→∞f(n)g(n)<∞.\limsup_{n \to \infty} \frac{f(n)}{g(n)} < \infty .

A limit of 00 says more than OO does. It says that ff is negligible against gg rather than merely bounded by a multiple of it, and that stronger relation is written f=o(g)f = o(g); so 4x3+7x=o(x4)4x^3 + 7x = o(x^4), while 3n2+5n+7=O(n2)3n^2 + 5n + 7 = O(n^2) is not o(n2)o(n^2).

Remark (What $O$ does and does not see).

The notation is insensitive to scaling: if f=O(g)f = O(g) then Cf=O(Dg)Cf = O(Dg) for any non-zero constants CC and DD. It is equally insensitive to any finite initial stretch of the two functions. What it describes is therefore the asymptotic behaviour of ff and gg, their behaviour for large inputs rather than at any one input. Constant factors depend on the machine and the compiler, which we do not model, so this is the level of precision we want; for the same reason an OO bound alone does not decide which of two programs to run on a given machine.

The Cost of Schoolbook Arithmetic

We all learned at school how to add and multiply natural numbers written in decimal: once m+nm + n and mnmn are known for the single digits 0⩽m,n⩽90 \leqslant m, n \leqslant 9, sums and products of arbitrary numbers follow by working column by column and carrying. These schoolbook methods are algorithms in the sense of the first chapter, and we can count their cost.

Remark (The base does not matter).

There is nothing special about ten here. The same algorithms work, with minor changes, in any base, binary included. Binary is convenient for multiplication, because the table of single-digit products that has to be known in advance is very much smaller.

Throughout this section the size of the input is the number of digits, which for xx written in base bb is ⌊log⁡bx⌋+1\lfloor \log_b x \rfloor + 1 by Corollary 2.22.

Proposition 3.23 (Cost of schoolbook addition).

Fix a base b⩾2b \geqslant 2. Computing x+yx + y by the schoolbook algorithm takes O(n)O(n) elementary operations, where nn is the larger of the numbers of base-bb digits of xx and of yy.

Discussion.

The algorithm treats one column at a time, and each column costs at most a fixed amount, however large the numbers are. A single column adds three quantities: the digit of xx there, the digit of yy there, and the carry coming in from the column to its right. The first two lie in {0,…,b−1}\{0, \ldots, b-1\} and the third is 00 or 11, so there are at most 2b22b^2 possible columns to deal with, a number fixed once bb is fixed and independent of nn. So each column costs at most some constant C=C(b)C = C(b), there are nn columns and at most one extra step to write a final carry, and the count is Cn+CCn + C. Padding the shorter number with leading zeros makes both numbers nn digits long.

Proof.

Write xx and yy in base bb, padding the shorter expansion with leading zeros so that both have nn digits.

The algorithm works through the columns from right to left. At each column it adds three quantities: the digit of xx in that column, the digit of yy in that column, and the carry from the preceding column. The two digits lie in {0,…,b−1}\{0, \ldots, b-1\} and the carry is 00 or 11, so there are finitely many possible single-column computations, their number depending on bb alone. Since bb is fixed, there is a constant CC such that every single-column computation takes at most CC elementary operations.

The algorithm performs one such computation for each of the nn columns and at most one further step to write a final carry, so its running time is at most Cn+CCn + C. By the proposition on polynomials this is O(n)O(n).

No algorithm does better than O(n)O(n) here, since the output has about nn digits and writing it down takes that long. Schoolbook multiplication costs more.

Proposition 3.24 (Cost of schoolbook multiplication).

Fix a base b⩾2b \geqslant 2. Computing xyxy by the schoolbook algorithm takes O(n2)O(n^2) elementary operations, with nn as above.

Discussion.

The algorithm has two stages, bounded separately. In the first it forms one partial product for each digit of yy: multiplying the whole of xx by a single digit is a column-by-column pass of the kind already costed, so it is O(n)O(n), and there are nn digits of yy, giving O(n2)O(n^2). Shifting a partial product left only decides where its digits are written. In the second stage the nn partial products are added, and each addition involves numbers of at most 2n2n digits, so by the previous proposition each costs O(n)O(n) and the nn of them cost O(n2)O(n^2). Two stages of O(n2)O(n^2) make O(n2)O(n^2).

Proof.

Write xx and yy in base bb, padded to nn digits each.

The algorithm forms nn partial products, one for each digit yjy_j of yy. Forming the one belonging to yjy_j means multiplying yjy_j by each of the nn digits of xx, one column at a time and carrying where needed, and then shifting the result jj places to the left to obtain x⋅yjb jx \cdot y_j b^{\,j}. Each single-digit product with a carry is one of finitely many computations depending on bb alone, so each partial product costs O(n)O(n) and all nn of them cost O(n2)O(n^2).

It then adds the nn partial products. Each is at most 2n2n digits long, so by the previous proposition each addition costs O(n)O(n), and nn of them cost O(n2)O(n^2).

Both stages are O(n2)O(n^2), so there are constants bounding each by a multiple of n2n^2 beyond some point; adding the two bounds gives a constant bounding the total, and the running time is O(n2)O(n^2).

Remark (Faster multiplication).

Multiplication algorithms asymptotically better than the schoolbook one do exist, though they are a great deal more complicated. The first was found by Karatsuba in 1960 and multiplies two nn-digit numbers in O(nα)O(n^{\alpha}) operations with α=log⁡3/log⁡2≈1.58\alpha = \log 3 / \log 2 \approx 1.58. More recently Harvey and van der Hoeven gave an O(nlog⁡n)O(n \log n) algorithm, which is believed to be the best possible.

This last result also shows a limitation of the notation. The running time is bounded by Cnlog⁡nCn\log n for some constant CC, but CC is very large: the algorithm beats its rivals only on enormous inputs, and at the sizes that arise in practice methods with worse asymptotic behaviour and smaller constants are faster.

The Cost of Trial Division

Remark (The cost of the obvious method).

The definition of primality suggests testing, for a given n⩾2n \geqslant 2, every pair a,ba, b with 2⩽a,b⩽n/22 \leqslant a, b \leqslant n/2 to see whether n=abn = ab. That is up to (n/2−1)2(n/2 - 1)^2 multiplications. Today’s machines perform several billion operations a second, and even so an eight-digit input on this method would occupy one of them for several hours.

Trial division does much better.

Proposition 3.25 (Cost of trial division).

Deciding whether n⩾2n \geqslant 2 is prime by testing every candidate divisor from 22 to ⌊n⌋\lfloor \sqrt{n} \rfloor takes O(n)O(\sqrt{n}) elementary operations.

Discussion.

Each pass of the loop performs a fixed amount of work, one remainder and one comparison, both elementary, so the running time is a constant multiple of the number of passes. The loop runs over the integers from 22 to ⌊n⌋\lfloor \sqrt n \rfloor and may stop early at a divisor, so the count is at most ⌊n⌋−1\lfloor \sqrt n \rfloor - 1; the floor is at most n\sqrt n, and the constants are absorbed by the OO. That stopping at n\sqrt n rather than at n−1n - 1 is correct is the proposition on small divisors.

Proof.

By the proposition on small divisors, nn is composite exactly when some dd with 2⩽d⩽n2 \leqslant d \leqslant \sqrt{n} divides it, so the loop over d=2,…,⌊n⌋d = 2, \ldots, \lfloor \sqrt{n} \rfloor decides the question. It makes at most ⌊n⌋−1⩽n\lfloor \sqrt{n}\rfloor - 1 \leqslant \sqrt{n} passes, and each pass computes one remainder and one comparison, which is a bounded number CC of elementary operations. The total is at most CnC\sqrt{n}, which is O(n)O(\sqrt{n}).

Remark (Avoiding the square root).

It is not obvious that ⌊n⌋\lfloor \sqrt{n} \rfloor can itself be computed with an elementary operation, and the algorithm need not compute it. Increasing the candidate ii by 11 and stopping as soon as i⋅i>ni \cdot i > n tests exactly the same candidates and uses only multiplication and comparison. Every step of the algorithm is then well defined for every admissible input, the algorithm stops after finitely many steps, and it returns the right answer, so it computes the function f:N→{yes,no}f : \mathbb{N} \to \{\text{yes}, \text{no}\} taking the value yes exactly at the primes. We write {true,false}\{\text{true}, \text{false}\} or {0,1}\{0, 1\} for the two values as well.

Remark (Fast in $n$, slow in the input).

An O(n)O(\sqrt{n}) bound looks good, but nn is not the size of the input. The instance handed to the algorithm is the digit string of nn, whose length is m=⌊log⁡bn⌋+1m = \lfloor \log_b n \rfloor + 1, so nn is about bmb^{m} and n\sqrt{n} is about bm/2b^{m/2}. Measured against the length of its input, trial division takes exponentially many steps, so a three-hundred-digit number is out of its reach at any realistic speed.

Problem 3.18.

Give the running time of each of the following in OO notation, as a function of nn, and justify each answer.

  1. Summing the integers from 11 to nn with a loop.
  2. Summing the integers from 11 to nn with the closed form.
  3. Printing every pair (i,j)(i, j) with 1⩽i<j⩽n1 \leqslant i < j \leqslant n.
  4. Extracting the base-bb digits of nn by repeated division.

Problem 3.19.

A product xyxy may be computed by adding xx to a running total yy times, using no multiplication at all. Let xx and yy have nn digits in base bb.

  1. Give the running time of this method as a function of nn and bb.
  2. Evaluate that count and the n2n^2 of the schoolbook algorithm at b=10b = 10 for n=1n = 1, n=3n = 3 and n=20n = 20. On a machine performing 10910^9 operations a second, say for which of the three the repeated-addition method is still usable.
  3. Both methods perform additions of nn-digit numbers, and only one of them is out of reach for a twenty-digit input. Say which quantity in your answer to the first part is responsible, and why measuring the input by nn rather than by yy is what makes the difference visible.

Problem 3.20.

Two programs settle the same problem on inputs of size nn. The first performs 100n100n elementary operations, the second n2/100n^2/100.

  1. Find every nn at which the second is the faster, and the size at which the first overtakes it.
  2. Give the running time of each in OO notation, and say what those two statements do and do not tell you about which program to run.
  3. A machine performs 10910^9 operations a second and no input ever exceeds n=5000n = 5000. Which program should be run, and how long does it take?

Search and Approximation

The loops of the previous chapters mostly checked something: whether any candidate divides nn, whether every character of a string is a digit. The loops of this chapter search for a value: they try candidates until one works.

Exhaustive Enumeration

The simplest search strategy is the one the primality test already used: try every candidate in some collection until one works. When the collection is finite, or can be made finite by bounding the range, this is exhaustive enumeration, and it finds a solution whenever one exists.

Cube Roots

Suppose we want the integer cube root of a perfect cube. Given an integer nn, we seek an integer xx with x3=nx^3 = n, or a report that no such integer exists.

The strategy is direct: test x=0,1,2,…x = 0, 1, 2, \ldots in order until either x3=∣n∣x^3 = |n|, a success, or x3>∣n∣x^3 > |n|, a failure, the latter being conclusive because a<ba < b implies a3<b3a^3 < b^3 for non-negative integers. For negative nn the cube root is the negative of the cube root of ∣n∣|n|.

n = 27
x = 0

while x ** 3 < abs(n):
    x = x + 1

if x ** 3 != abs(n):
    print(f'{n} is not a perfect cube')
else:
    if n < 0:
        x = -x
    print(f'Cube root of {n} is {x}')

# Cube root of 27 is 3

Hand simulating for n=27n = 27:

Test evaluationxx ** 3x ** 3 < 27
1st00True
2nd11True
3rd28True
4th327False

The loop stops with x=3x = 3, and since 33=27=∣n∣3^3 = 27 = |n| the program reports a cube.

Problem 3.21.

Trace the program above for n=8n = 8, n=−8n = -8 and n=9n = 9. In each case build the hand-simulation table, state the value of x when the loop stops, and say whether the program reports a perfect cube.

Example 3.26 (The speed of exhaustive search).

Take n=1,957,816,251n = 1{,}957{,}816{,}251. Its cube root is 1,2511{,}251, so the loop makes 1,2511{,}251 passes and finishes at once. Take instead n=7,406,961,012,236,344,616n = 7{,}406{,}961{,}012{,}236{,}344{,}616, whose cube root is 1,949,3061{,}949{,}306: the loop makes nearly two million passes and still finishes in well under a second. At billions of instructions a second, a million passes take a fraction of a second.

Termination and Decrementing Functions

Every loop in a correct program must stop, unless it is deliberately endless. To prove that one does, we exhibit a quantity that strictly decreases at every pass and cannot go below zero.

Definition 3.27 (Decrementing function).

A decrementing function for a loop is an integer-valued expression DD in the program’s variables such that

  1. D⩾0D \geqslant 0 whenever the loop test holds, and
  2. DD strictly decreases at every pass of the body.

A loop admitting a decrementing function stops after at most D0D_0 passes, where D0D_0 is the initial value of DD.

With it, “the loop eventually stops” can be proved. For the cube-root search a suitable choice is D=⌈∣n∣1/3⌉−xD = \lceil |n|^{1/3}\rceil - x, written with the ceiling of the last lesson, which may also be had from the floor as ⌈x⌉=−⌊−x⌋\lceil x\rceil = -\lfloor -x\rfloor.

Theorem 3.28 (Termination of the cube-root search).

The exhaustive cube-root search stops for every integer nn.

Discussion.

The variable xx increases rather than decreases, so it is not itself a decrementing function; what decreases is the distance from xx to the value at which the loop stops, and the loop stops once x3x^3 reaches ∣n∣|n|, that is, once xx reaches ∣n∣1/3|n|^{1/3}. Since xx is an integer this value is rounded up, which is where the ceiling enters. The two conditions of the definition then have to be checked separately: non-negativity comes from the loop test, since the test holding means xx has not yet reached the target, and the strict decrease comes from the body, since the body adds exactly 11 to xx and does not change the target.

Proof.

Put D=⌈∣n∣1/3⌉−xD = \lceil |n|^{1/3} \rceil - x. Initially x=0x = 0, so D=⌈∣n∣1/3⌉⩾0D = \lceil |n|^{1/3}\rceil \geqslant 0.

Suppose the loop test x3<∣n∣x^3 < |n| holds. Then x<∣n∣1/3⩽⌈∣n∣1/3⌉x < |n|^{1/3} \leqslant \lceil |n|^{1/3}\rceil, and both sides being integers gives D⩾1D \geqslant 1, so in particular D⩾0D \geqslant 0.

Each pass replaces xx by x+1x + 1 and changes nothing else, so DD falls by exactly 11. Being a non-negative integer that falls by 11 each pass, DD can do so at most ⌈∣n∣1/3⌉\lceil |n|^{1/3}\rceil times, and the loop stops after at most that many passes.

Note (Decrementing functions as a diagnostic).

When a program appears to run forever, the definition suggests where to look. Identify the quantity that ought to be decreasing; if there is none, the loop has no decrementing function, which suggests that the loop may never end. Otherwise print the candidate at every pass, and if the printed values fail to fall, the fault is in the body. Deleting x = x + 1 from the cube-root search, for instance, leaves D=⌈∣n∣1/3⌉D = \lceil |n|^{1/3}\rceil at every pass, constant instead of decreasing.

Remark (The cost of exhaustive enumeration).

Exhaustive enumeration is correct and simple, but it can be slow. The cube-root search tests ⌈∣n∣1/3⌉\lceil |n|^{1/3}\rceil candidates, so its running time is O(n1/3)O(n^{1/3}). For n=1012n = 10^{12} that is 10410^4 passes, which is quick; for n=1030n = 10^{30} it is 101010^{10} passes, minutes or hours. Bisection search needs about a hundred steps for the same nn.

Approximate Solutions

The cube-root search demands an exact integer answer, so it works only on perfect cubes. Many numerical problems have no exact answer in the integers, or even in the rationals: 2\sqrt{2} is irrational, and no finite program returns it. We ask instead for an approximate answer, within a stated tolerance.

Definition 3.29 (ε\varepsilon-approximation).

Let ff be a function, yy a target value and ε>0\varepsilon > 0 a prescribed tolerance. An ε\varepsilon-approximation to a solution of f(x)=yf(x) = y is a value x^\hat{x} with

∣f(x^)−y∣<ε.|f(\hat{x}) - y| < \varepsilon .

The tolerance is chosen by whoever writes the program. A smaller ε\varepsilon demands a more accurate answer, and can take much more computation.

Exhaustive Search for Square Roots

Exhaustive enumeration adapts to the approximate setting. Rather than the integers 0,1,2,…0, 1, 2, \ldots we test the values 0,δ,2δ,3δ,…0, \delta, 2\delta, 3\delta, \ldots for a small step δ>0\delta > 0, and accept the first x^\hat{x} with ∣x^2−n∣<ε|\hat{x}^2 - n| < \varepsilon.

n = 25
epsilon = 0.01
step = 0.0001
guess = 0.0
num_guesses = 0

while abs(guess ** 2 - n) >= epsilon and guess <= n:
    guess = guess + step
    num_guesses = num_guesses + 1

if abs(guess ** 2 - n) >= epsilon:
    print(f'Failed to find sqrt({n})')
else:
    print(f'{guess} is close to sqrt({n})')
    print(f'Number of guesses: {num_guesses}')

# 4.999000000001688 is close to sqrt(25)
# Number of guesses: 49990

Roughly n/δn/\delta candidates are tested before the neighbourhood of n\sqrt{n} is reached, here 49,99049{,}990 of them. The answer is not 55: 4.9992=24.9900014.999^2 = 24.990001 is within ε=0.01\varepsilon = 0.01 of 2525, which is all that was asked.

Example 3.30 (When the search space misses the answer).

Run the same program with n=0.25n = 0.25:

n = 0.25
epsilon = 0.01
step = 0.0001
guess = 0.0

while abs(guess ** 2 - n) >= epsilon and guess <= n:
    guess = guess + step

if abs(guess ** 2 - n) >= epsilon:
    print(f'Failed to find sqrt({n})')

# Failed to find sqrt(0.25)

The search fails because 0.25=0.5\sqrt{0.25} = 0.5, while the guard guess <= n stops it at 0.250.25. The guard was written for n⩾1n \geqslant 1, where n⩽n\sqrt{n} \leqslant n; for 0<n<10 < n < 1 we have n>n\sqrt{n} > n and the upper bound has to be raised.

Example 3.31 (When the step is too large).

Now take n=123,456n = 123{,}456 with the same δ=0.0001\delta = 0.0001. The program runs a long time and then reports failure: the step carries it over every value within ε\varepsilon of 123456≈351.363\sqrt{123456} \approx 351.363 without ever landing on one. Shrinking δ\delta to 10−610^{-6} repairs that and obliges the program to test some 351,000,000351{,}000{,}000 candidates. Starting nearer the answer would help, and presumes we already know roughly where the answer is.

The step δ\delta controls both the accuracy and the running time, in opposite directions: a smaller step is more accurate and slower.

Exhaustive enumeration does not use whether x^2\hat{x}^2 was too small or too large. Bisection uses that comparison to discard half the remaining candidates at every step.

Looking up a word in a dictionary works the same way: open it near the middle, and if the word comes later than the page shown, discard the first half and open the second half near its middle.

We state the algorithm on an interval, in the notation of the first lesson.

Definition 3.32 (Bisection search).

Let ff preserve order on [ℓ,h][\ell, h], so that a<ba < b implies f(a)<f(b)f(a) < f(b) throughout. Bisection search solves f(x)=yf(x) = y by maintaining the invariant that a solution lies in [ℓ,h][\ell, h] and halving the interval:

  1. compute the midpoint m=(ℓ+h)/2m = (\ell + h)/2;
  2. if f(m)f(m) is too large, replace hh by mm; if f(m)f(m) is too small, replace ℓ\ell by mm;
  3. repeat until ∣f(m)−y∣<ε|f(m) - y| < \varepsilon.

After kk steps the interval has width (h0−ℓ0)/2k(h_0 - \ell_0)/2^{k}, where h0−ℓ0h_0 - \ell_0 is its initial width.

0123450510152025midpoint of the surviving interval
Figure 3.3. The first six steps of a bisection search for 25\sqrt{25} on [0,25][0, 25]. Each bar is the interval still under consideration, the dot on it is the midpoint tested, and the dashed line marks the root at x=5x = 5.

Implementation

n = 25
epsilon = 0.01
low = 0.0
high = max(1.0, n)
guess = (low + high) / 2.0
num_guesses = 0

while abs(guess ** 2 - n) >= epsilon:
    num_guesses = num_guesses + 1
    if guess ** 2 < n:
        low = guess
    else:
        high = guess
    guess = (low + high) / 2.0

print(f'{guess} is close to sqrt({n})')
print(f'Number of guesses: {num_guesses}')

# 5.00030517578125 is close to sqrt(25)
# Number of guesses: 13

Exhaustive enumeration needed about fifty thousand guesses; bisection needs thirteen. The upper end is max(1.0, n) rather than n, so that n\sqrt{n} lies in [0,h][0, h] whether n⩾1n \geqslant 1 or 0<n<10 < n < 1, which repairs the failure on n=0.25n = 0.25.

Example 3.33 (Hand simulating the bisection).

The first four steps for 25\sqrt{25} on [0,25][0, 25]:

Steplowhighguessguess ** 2Action
00.025.012.5156.25too high, high = 12.5
10.012.56.2539.0625too high, high = 6.25
20.06.253.1259.765625too low, low = 3.125
33.1256.254.687521.972656too low, low = 4.6875

Four steps have taken the interval from width 2525 to width 6.25−4.6875=1.56256.25 - 4.6875 = 1.5625, and eight more bring it below 0.010.01.

Example 3.34 (Bisection on a larger input).

Exhaustive approximation failed on n=123,456n = 123{,}456 because no step served: too large and it skipped the root, too small and it needed hundreds of millions of guesses. Bisection starts from [0,123456][0, 123456] and, by the theorem below, needs at most ⌈log⁡2(123456/0.01)⌉=⌈log⁡212,345,600⌉=24\lceil \log_2(123456/0.01)\rceil = \lceil \log_2 12{,}345{,}600 \rceil = 24 guesses to narrow the interval that far. Run, it stops after 3030, the excess coming from the test being on ∣x^2−n∣|\hat{x}^2 - n| rather than on the width of the interval.

Convergence

Bisection produces guesses m0,m1,m2,…m_0, m_1, m_2, \ldots, and each step halves the interval holding the root, so the distance from the guess to the root falls by a factor of two every time. The bound this gives is stated with the logarithm to base 22, written log⁡2N\log_2 N: the power to which 22 must be raised to give NN, so that log⁡28=3\log_2 8 = 3 and log⁡21024=10\log_2 1024 = 10, and log⁡22500≈11.29\log_2 2500 \approx 11.29 for an NN that is not a power of two.

Theorem 3.35 (Convergence of bisection search).

Let [a,b][a, b] be the initial interval and ε>0\varepsilon > 0 the tolerance. Bisection search narrows the interval below ε\varepsilon after at most ⌈log⁡2((b−a)/ε)⌉\lceil \log_2((b-a)/\varepsilon)\rceil steps.

Discussion.

The proof follows the width of the interval. One step replaces the interval by one of its two halves, so the width is halved whichever half survives, and after kk steps it is the initial width divided by 2k2^k. It remains to find the least kk for which this is below ε\varepsilon, by taking log⁡2\log_2 of both sides; the ceiling appears because kk counts steps and must be an integer. The argument resembles the one for decrementing functions, with a width halved at each step in place of a counter decreased by one, and so the number of steps is logarithmic rather than linear.

Proof.

Each step replaces [ℓ,h][\ell, h] by either [ℓ,m][\ell, m] or [m,h][m, h] with mm the midpoint, so the new width is half the old one. After kk steps the width is therefore (b−a)/2k(b-a)/2^{k}, and since the midpoint of an interval of width ww is within w/2w/2 of every point of it, the guess is within (b−a)/2k+1(b-a)/2^{k+1} of the root.

We need (b−a)/2k<ε(b-a)/2^{k} < \varepsilon, that is 2k>(b−a)/ε2^{k} > (b-a)/\varepsilon, that is

k>log⁡2 ⁣(b−aε).k > \log_2\!\left(\frac{b-a}{\varepsilon}\right) .

The least integer meeting this is ⌈log⁡2((b−a)/ε)⌉\lceil \log_2((b-a)/\varepsilon)\rceil.

Example 3.36 (Checking the bound).

For 25\sqrt{25} on [0,25][0, 25] with ε=0.01\varepsilon = 0.01 the theorem gives

⌈log⁡2 ⁣(250.01)⌉=⌈log⁡22500⌉=⌈11.29⌉=12,\left\lceil \log_2\!\left(\frac{25}{0.01}\right) \right\rceil = \lceil \log_2 2500 \rceil = \lceil 11.29 \rceil = 12 ,

and the program used 1313: the test is on ∣x^2−n∣|\hat{x}^2 - n| rather than on the width, and floating-point rounding can add a step.

Remark (Logarithmic against linear).

Exhaustive enumeration with step δ\delta tests about n/δn/\delta candidates; bisection tests about log⁡2(n/ε)\log_2(n/\varepsilon). For n=109n = 10^9 and ε=0.01\varepsilon = 0.01 that is of the order of 101110^{11} guesses against roughly 3737. Doubling the range doubles the work of the first and adds a single step to the second.

Problem 3.22.

Adapt the bisection program to approximate 273\sqrt[3]{27} to within ε=0.001\varepsilon = 0.001. How many guesses does it need? Compare that with the number exhaustive enumeration with step δ=0.001\delta = 0.001 would need, and with the bound of the theorem.

Machine Arithmetic

The methods above assume that arithmetic on reals is exact: that 0.1+0.1+0.10.1 + 0.1 + 0.1 is 0.30.3, that halving an interval halves it, and that the only error is the tolerance we chose. On a machine none of these holds exactly.

Binary Fractions

A computer stores numbers in binary. Just as the decimal system writes fractions with negative powers of ten, so that 0.375=3/10+7/100+5/10000.375 = 3/10 + 7/100 + 5/1000, the binary system uses negative powers of two:

(0.b1b2b3…)2=b12+b24+b38+⋯ .(0.b_1 b_2 b_3 \ldots)_2 = \frac{b_1}{2} + \frac{b_2}{4} + \frac{b_3}{8} + \cdots .

Example 3.37 (Exact binary fractions).

The decimal 0.3750.375 has an exact binary form:

0.375=14+18=0⋅12+1⋅14+1⋅18=(0.011)2.0.375 = \frac{1}{4} + \frac{1}{8} = 0 \cdot \tfrac{1}{2} + 1 \cdot \tfrac{1}{4} + 1 \cdot \tfrac{1}{8} = (0.011)_2 .

Likewise 0.5=(0.1)20.5 = (0.1)_2 and 0.625=1/2+1/8=(0.101)20.625 = 1/2 + 1/8 = (0.101)_2. These are exact because their denominators are powers of two: 0.375=3/80.375 = 3/8, 0.5=1/20.5 = 1/2 and 0.625=5/80.625 = 5/8.

Not every decimal fraction has a finite binary form.

Theorem 3.38 (One tenth is not a finite binary fraction).

The number 1/101/10 has no finite binary representation.

Discussion.

A finite binary fraction has a power of two as its denominator, and the proof compares that with the factor 55 in 1010. Suppose the representation existed with kk bits. Multiplying it through by 2k2^{k} clears every denominator at once and leaves an integer on the right, so the supposed identity becomes 2k=10M2^{k} = 10M for an integer MM. The right-hand side is divisible by 55 and the left is a power of two, and no power of two has 55 among its factors. The same argument gives the general case: a fraction in lowest terms is a finite binary fraction exactly when its denominator is a power of two.

Proof.

Suppose 1/10=(0.b1b2…bk)21/10 = (0.b_1 b_2 \ldots b_k)_2 for some bits b1,…,bkb_1, \ldots, b_k. Then

110=b12+b24+⋯+bk2k=M2k,M=b12k−1+b22k−2+⋯+bk∈N0.\frac{1}{10} = \frac{b_1}{2} + \frac{b_2}{4} + \cdots + \frac{b_k}{2^{k}} = \frac{M}{2^{k}}, \qquad M = b_1 2^{k-1} + b_2 2^{k-2} + \cdots + b_k \in \mathbb{N}_0 .

Cross-multiplying gives 2k=10M2^{k} = 10M, so 55 divides 2k2^{k}. But 2k2^{k} is a product of kk factors of 22, and 55 is a prime different from 22, so 55 divides no power of 22. The supposition is therefore false.

The same holds in general: a rational p/qp/q in lowest terms has a finite binary expansion exactly when qq is a power of two, and since 10=2⋅510 = 2 \cdot 5, the fraction 1/101/10 needs an infinite repeating binary expansion, just as 1/3=0.333…1/3 = 0.333\ldots repeats for ever in decimal.

What This Costs in Practice

Python’s float uses the IEEE 754 double-precision format: 6464 bits carrying a sign, an 1111-bit exponent and a 5252-bit significand with one further bit implied. That is about 1515 to 1717 significant decimal digits.

The consequence is that a decimal constant which looks exact in the source is silently rounded to the nearest representable binary fraction. Putting the format specifier :.20f inside an f-string prints twenty digits after the point and exposes it:

print(0.1)              # 0.1                     (the display is rounded)
print(f'{0.1:.20f}')    # 0.10000000000000000555

print(0.1 + 0.1 + 0.1 == 0.3)     # False
print(f'{0.1 + 0.1 + 0.1:.20f}')  # 0.30000000000000004441
print(f'{0.3:.20f}')              # 0.29999999999999998890

The sum overshoots by about 5.5×10−175.5 \times 10^{-17}, while the literal 0.3 is itself rounded downwards from three tenths. The two errors go opposite ways, so == returns False.

Example 3.39 (A loop that never lands).

Counting from 00 to 11 in steps of a tenth:

x = 0.0
count = 0

while x != 1.0:
    x = x + 0.1
    count = count + 1
    if count > 20:
        print('Gave up')
        break

The loop never stops of its own accord. After ten additions x is 0.9999999999999999, not 1.0; the eleventh takes it to 1.0999999999999999, and it has stepped over 1.0 without touching it. Replacing != by < 1.0, or by abs(x - 1.0) >= epsilon, repairs it.

Remark (Never compare floats with $==$).

Do not test floating-point numbers for exact equality. The expression x == 0.3 is almost certainly wrong even when x was computed by a formula mathematically equal to three tenths. Test instead that the difference is small:

x = 0.1 + 0.1 + 0.1
print(abs(x - 0.3) < 1e-9)   # True

This is an ε\varepsilon-approximation applied to equality; the tolerance should match the precision of the computation, not of the display.

Example 3.40 (Rounding error accumulates).

Adding a tenth a thousand times:

total = 0.0

for i in range(1000):
    total = total + 0.1

print(f'{total:.20f}')     # 99.99999999999859312538
print(total == 100.0)      # False
print(abs(total - 100.0))  # about 1.4e-12

Each addition contributes a rounding error of the order of 10−1710^{-17}, and over a thousand additions they accumulate to about 10−1210^{-12}: small, but enough to make an exact test fail.

Remark (When it matters).

For the bisection search this error is harmless: an approximate answer was wanted, and ε\varepsilon is much larger than the rounding. The problem arises in code that assumes exact arithmetic, by testing x == 0.0 where it should test abs(x) < epsilon, or by expecting a counter incremented by 0.1 to arrive exactly at 1.0 after ten steps.

Problem 3.23.

The theorem above rules out 1/101/10, and the paragraph following it makes the general claim: a rational p/qp/q in lowest terms has a finite binary expansion exactly when qq is a power of two. Prove both directions, following the argument of the theorem. Then say how many bits the expansion of p/2kp/2^{k} needs when pp is odd, and give the expansions of 7/167/16 and 5/65/6 as far as each can be written.

Problem 3.24.

The loop of the example above adds 0.10.1 a thousand times and lands about 1.4×10−121.4 \times 10^{-12} short of 100100.

  1. Write a second program that adds the integer 11 a thousand times and divides by ten at the end, and compare its result with 100.0 using ==.
  2. Say why the second is exact where the first is not, in terms of which numbers have a finite binary expansion.
  3. A sum of money is to be accumulated over many transactions, each an exact number of pounds and pence. Say which of the two arrangements should be used, and what the other would cost after a million transactions.

Exercises on Types and Expressions

Exercise 3.1.

Give the type and the value of each expression.

  1. 7 / 2;
  2. 7 // 2;
  3. 7 % 2;
  4. 7.0 // 2;
  5. 2 ** 0.5;
  6. 1 == 1.0.

Exercise 3.2.

State the value of 2 ** 3 ** 2, (2 ** 3) ** 2, -2 ** 2 and (-2) ** 2, and say which rule of precedence or associativity settles each one.

Exercise 3.3.

Predict the value of 0.1 + 0.2 == 0.3 and of 0.5 + 0.25 == 0.75, then check both. Explain why the two come out differently, and give another pair of float values whose sum can be tested exactly.

Exercise 3.4.

Let n be a positive integer.

  1. Write an expression for its last two decimal digits.
  2. Write an expression for the digit in its hundreds place.
  3. Write an expression that is True exactly when n is a multiple of 33 but not of 99.

Exercise 3.5.

Give the value of int('101', b) for b=2,3,8b = 2, 3, 8 and 1616, and find the base bb for which it equals 122122.

Exercise 3.6.

Explain why x != 0 and 100 % x == 0 may be evaluated for any integer x, while 100 % x == 0 and x != 0 may not.

Exercise 3.7.

Explain what a, b = b, a does, and why performing a = b and then b = a does not do the same. Say what the second pair leaves in a and b.

Exercises on Branching

Exercise 3.8.

Write a program that reads three integers and prints them in increasing order, using conditional statements only and no built-in sorting.

Exercise 3.9.

A year is a leap year when it is divisible by 44, except that centuries are not, except that those divisible by 400400 are. Write a program that reads a year and reports whether it is a leap year, and check it on 19001900, 20002000, 20232023 and 20242024.

Exercise 3.10.

Write a program that reads a real number x and prints which of the intervals (−∞,−1)(-\infty, -1), [−1,0)[-1, 0), [0,1][0, 1] and (1,∞)(1, \infty) contains it.

Exercise 3.11.

Write a program that reads three numbers a, b and c and prints how many of them are strictly positive. Use branching only, with no loop and no arithmetic on the results of the tests.

Exercise 3.12.

The expressions 1/x if x != 0 else 0 and (x != 0) * (1/x) agree for every non-zero x. Say what each does when x is 0, and explain the difference in terms of which arms of a case split get evaluated.

Exercise 3.13.

Explain why a branching program of NN statements performs at most NN atomic operations on any input, and give a three-statement program whose count of operations depends on its input.

Exercise 3.14.

Write a program that reads three positive reals and reports whether they can be the side lengths of a triangle, that is, whether each of them is smaller than the sum of the other two. Use conditional statements only.

Exercises on Iteration

Exercise 3.15.

Write a program that computes n!n! for a given n⩾0n \geqslant 0 with a for loop, and a second that does the same with a while loop. State how many multiplications each performs, and say what each returns for n=0n = 0.

Exercise 3.16.

Write a program that counts the digits of nn in base bb with a loop, for n∈Nn \in \mathbb{N} and b>1b > 1. Check its count against Corollary 2.22 for several nn and bb.

Exercise 3.17.

Write a program that sums the decimal digits of a positive integer using // and % only, with no strings. Extend it to repeat the process on the result until a single digit is left, and compare that digit with n mod 9n \bmod 9.

Exercise 3.18.

Write a program that prints the first nn Fibonacci numbers, using a single multiple assignment inside the loop to advance the pair.

Exercise 3.19.

Write a program that takes positive integers aa and bb and repeatedly replaces the pair by the smaller number and the remainder of the larger on division by it, stopping when the remainder is 00. Hand simulate the loop on (48,18)(48, 18), and identify the surviving number as the largest integer dividing both aa and bb.

Exercise 3.20.

Write a program that prints every pair (a,b)(a, b) with 1⩽a<b⩽N1 \leqslant a < b \leqslant N and a∣ba \mid b, for a given NN. Say how many divisibility tests it performs as a function of NN.

Exercise 3.21.

Modify the trial-division program so that, when nn is composite, it also prints the least divisor of nn above 11. Run it on three numbers near one million of your own choosing, and say which of them are prime.

Exercise 3.22.

Write a program that converts a string of base-bb digits to an integer with a loop, for a given b>1b > 1, using Horner’s scheme rather than forming any power of bb. Count the multiplications, and check the result against int.

Exercise 3.23.

Write a program that runs the loop replacing nn by n/2n/2 when nn is even and by 3n+13n + 1 when nn is odd, stopping when nn reaches 11, and counts its passes. Run it on each starting value from 11 to 3030, and report which takes the most.

Exercises on Running Time

Exercise 3.24.

Show directly from the definition that ∑k=1nk=O(n2)\sum_{k=1}^{n} k = O(n^2), giving an explicit α\alpha and n0n_0, and that n2=O(∑k=1nk)n^2 = O\bigl(\sum_{k=1}^{n} k\bigr) as well, so that each of the two is OO of the other. Then do both again with the limit form.

Exercise 3.25.

Decide which of the following hold, with a proof or a counterexample in each case.

  1. 2n+1=O(2n)2^{n+1} = O(2^n);
  2. 22n=O(2n)2^{2n} = O(2^n);
  3. log⁡2n=O(log⁡10n)\log_2 n = O(\log_{10} n);
  4. nlog⁡2n=O(n2)n\log_2 n = O(n^2).

Exercise 3.26.

Suppose f1=O(g)f_1 = O(g) and f2=O(g)f_2 = O(g). Prove that f1+f2=O(g)f_1 + f_2 = O(g) and that c f1=O(g)c\,f_1 = O(g) for every constant c>0c > 0. Then give functions with f1=O(g)f_1 = O(g) and f2=O(g)f_2 = O(g) for which f1f2=O(g)f_1 f_2 = O(g) fails, so that OO is closed under sums and constant multiples but not under products.

Exercise 3.27.

An input of nn decimal digits denotes a number of size about 10n10^n. Express the running time of trial division as a function of the number of digits of its input rather than of the number itself, and say how many digits a number may have before an algorithm performing 10910^9 operations a second needs more than an hour.

Exercise 3.28.

Count the elementary operations performed by the schoolbook algorithms on two nn-digit numbers exactly, rather than up to OO: give the number of single-column additions performed by the addition, and the number of single-digit multiplications performed by the multiplication.

Exercises on Search and Approximation

Exercise 3.29.

Give a decrementing function for each of the following loops and state the bound on the number of passes it yields.

  1. while n >= 10: n = n // 10, for n∈Nn \in \mathbb{N};
  2. while a != b: a, b = (a - b, b) if a > b else (a, b - a), for positive integers aa and bb;
  3. the exhaustive square-root search of this chapter, whose body is guess = guess + step.

Exercise 3.30.

The positions of a string are numbered from 00. Write a program that prints the characters of a string my_str at the even positions, so that 'abcdefg' produces aceg. Write it twice, once with range and indexing, and once with a for loop over the characters and a counter.

Exercise 3.31.

Let NN be an integer with 0⩽N⩽10000 \leqslant N \leqslant 1000. Write a program that finds NN by bisection search, printing the number of guesses it needed and the value found. When the midpoint of the current interval falls between two integers, take the smaller.

Exercise 3.32.

A positive integer nn is a perfect power if n=rpn = r^{p} for integers r⩾1r \geqslant 1 and p⩾2p \geqslant 2. Write a program that reads nn and prints integers root and pwr with 1<pwr<61 < \text{pwr} < 6 and rootpwr=n\text{root}^{\text{pwr}} = n, or reports that no such pair exists. Test it on 6464, which admits 828^2, 434^3 and 262^6, on 2727, and on 7272.

Exercise 3.33.

Write a program that approximates log⁡2x\log_2 x for a positive real xx to within ε=0.001\varepsilon = 0.001 by bisection. It must first find an interval [L,H][L, H] containing log⁡2x\log_2 x: note that 20=12^0 = 1, that 2k2^{k} grows without bound, and that 2−k2^{-k} becomes arbitrarily small, so LL may have to be negative. Test it on x=1x = 1, x=32x = 32 and x=0.1x = 0.1.

Exercise 3.34.

Run the exhaustive square-root search and the bisection search on the same nn for ε=10−2,10−4\varepsilon = 10^{-2}, 10^{-4} and 10−610^{-6}, recording the number of guesses each makes. Say which of the two counts grows with 1/ε1/\varepsilon and which with log⁡(1/ε)\log(1/\varepsilon), and check the readings against the two bounds.

Exercise 3.35.

Find two float values a and b for which (a + b) + c and a + (b + c) differ for some c, and explain the difference in terms of rounding. What does this say about summing a list of numbers in different orders?

Exercise 3.36.

Which of the decimals 0.50.5, 0.20.2, 0.250.25, 0.30.3 and 0.1250.125 are exact as float values? Predict the answer from the theorem on binary fractions before checking each with :.20f.

An Applied Exercise

Exercise 3.37.

A band puts N=5000N = 5000 tickets on sale for one night and prices them dynamically. The first ticket costs p0=£40.00p_0 = \pounds 40.00. The tickets are sold in blocks of k=250k = 250, and after each block the price is raised by r=8%r = 8\% of the current price. The band will not charge more than a cap of C=£120.00C = \pounds 120.00: once a rise would take the price above the cap, the price is set to the cap and stays there for every remaining block.

  1. Write a program that sells all NN tickets under this rule and prints the price of each block together with the total revenue. Report the revenue.
  2. Give, in terms of p0p_0, rr and CC, the number of rises after which the uncapped price would first exceed the cap, and hence the number of the first block sold at the cap. Check your formula against the output of your program.
  3. Consider the sequence of price increases between consecutive blocks. Show that before the cap binds these form a geometric sequence and give its ratio. Exactly one increase belongs to neither the geometric stretch nor the capped stretch: identify it, give its value, and say what it would have been without the cap.
  4. Show that once the cap binds the total revenue is a linear function of the number of tickets sold, and give its slope. Say what the revenue would look like as a function of NN if the cap were removed.
  5. Give the running time of your program in OO notation as a function of NN and kk, and say which of the two the running time really depends on.
  6. Prices in pounds are not exact float values. Rewrite the simulation in integer pence, rounding each new price down to the nearest penny, and compare the two totals. The two disagree for two separate reasons; say which reason accounts for most of the gap, and which of the two totals the band should quote.

Check Yourself

 

Fresh questions on the whole lesson — none of them is worked out above. Work each one out on paper or in your head before opening Python; the box only tells you whether you got there.

Answers are checked in your browser, as often as you like. Nothing is sent anywhere and nothing is kept but your own work. A formula may be written with the symbols themselves or with ~ & | -> <-> ^, and \and, \or, \to expand as you type.

Exercise 3.38.

What is the type of 5 % 2 == 1?

answer one of these

Exercise 3.39.

What is the value of 7 // -2?

answer one of these

Exercise 3.40.

What is the value of -7 % 3?

answer one of these

Exercise 3.41.

What is the value of 9 // 2 ** 2?

answer one of these

Exercise 3.42.

After total = 0 and then total += 5 twice, what is total?

answer one of these

Exercise 3.43.

What is the value of 2 + 3 * 4 ** 2?

answer one of these

Exercise 3.44.

After x, y = 1, 2 and then x, y = y, x + y, what is y?

answer one of these

Exercise 3.45.

After x = 5, y = x and x = 7, what is y?

answer one of these

Exercise 3.46.

How many values does range(5, 20, 4) produce?

answer one of these

Exercise 3.47.

What is the last value produced by range(10, 3, -2)?

answer one of these

Exercise 3.48.

In a for i in range(3) loop whose body is a for j in range(i) loop, how many times does the inner body run in total?

answer one of these

Exercise 3.49.

Starting from n = 4096, how many times does the body of while n >= 10: n = n // 10 run?

answer one of these

Exercise 3.50.

Testing n=200n = 200 for primality by trial division, what is the largest candidate divisor that has to be tried?

answer one of these

Exercise 3.51.

What is the value of int('ff', 16)?

answer one of these

Exercise 3.52.

What are the binary digits of 2020?

answer one of these

Exercise 3.53.

Testing whether nn is prime by trial division up to ⌊n⌋\lfloor\sqrt{n}\rfloor has which running time?

answer one of these

Exercise 3.54.

Multiplying two nn-digit numbers by the schoolbook algorithm has which running time?

answer one of these

Exercise 3.55.

How many bisection steps are needed to narrow [0,64][0, 64] to a width below 11?

answer one of these

Exercise 3.56.

Starting from x = 0, how many times does the body of while x ** 3 < 64: x = x + 1 run?

answer one of these

Exercise 3.57.

What is the value of (3 > 2) or (1 / 0 == 0)?

answer one of these