← back to CS 115
Week 3Guttag §3229 min full read
6 concepts17 worked examples28 exercises4 exam-level6 figures
What are you here for?

03 Simple numerical programs (Chapter 3)

Start with this

One question before you read anything. Getting it wrong is the point: it shows you what this section is for.

§03.5 — whether two tenths added to one tenth is three tenths

Nothing on this page needs you to know this yet, and getting it wrong is the usual starting point. Decide whether the claim is true and give a reason in one sentence.

The claim: since 0.1, 0.2 and 0.3 are all written out exactly in the program text, the test 0.1 + 0.2 == 0.3 is True.

Find(a) True or false, with the reason.
Given
  • The three values are written as decimals in the source.

  • Nothing rounds them and nothing is typed in.

Hint 1/4

You are asked about a comparison, so the question is what the two sides actually hold, not what they are written as.

Hint 2/4

A float is stored as a . A tenth cannot be written as a sum of a few halves, quarters and eighths, so what gets stored is the nearest available value instead.

Hint 3/4

Here the left side is the sum of two stored values, each slightly off, and the right side is one stored value slightly off. Print the left side and you get 0.30000000000000004.

Hint 4/4

The claim is false, and the printed sum is the proof.

Show solution

Look at the two sides separately

$$\texttt{format(0.1, '.20f')}=\texttt{0.10000000000000000555}$$

The stored tenth is above a tenth, and the same is true of 0.2, so the sum is above 0.3 rather than below it.

$$\texttt{0.1 + 0.2}=\texttt{0.30000000000000004}$$

Python prints the shortest text that reads back as the stored value, and the long text is the clue that this is not 0.3.

Ask the useful question instead

$$\texttt{abs(a - 0.3) < 0.0001}$$

The distance is about 5.6e-17, which is far inside any window a program on this course would choose, so this test says True.

Answer $$\boxed{\text{False, and the distance is about } 5.6\times 10^{-17}}$$
Check

Repeat the check with an exact decimal: 0.25 + 0.25 == 0.5 is True, because quarters and halves are binary fractions. Same operator, same types, different decimals, so the operator is not at fault.

What this looks like in Spyder
The Variable Explorer tab showing a table with Name, Type, Size and Value columns for the names the program created.

The tab next to Help. It lists every name your program left behind with its type and its size, so it answers the one question a listing cannot: what is in this name right now. The stepper on this page shows the same four columns.

Spyder 5.5.1 on Linux, running the program in its own editor. A different version moves a few toolbar icons; the panes are named the same.

A question asks for the whole-number cube root of 729. One line looks like enough:

print(729 ** (1 / 3))
print(int(729 ** (1 / 3)))
print(27 ** (1 / 3))

Sample Run:

8.999999999999998
8
3.0

The answer written down is 8. Nothing went wrong, no message appeared, and the same line gives 3.0 for 27, so it looked tested.

By the end of this page you can write, from a blank file, a program that searches for a number instead of computing it, say in advance roughly how many passes it will take, and give the exact output of any short numerical program on this course's paper, digit for digit.

In 60 seconds

This week the loop stops printing and starts searching. Four methods, in order of how hard they work: try every candidate, try candidates a small step apart, halve the interval, use the shape of the curve. The rest of the page is the two questions every search answers, how close is close enough and what to say when nothing was found.

, and the check after it
$$\texttt{ans = 0}\;\rightarrow\;\texttt{while ans ** 3 < x: ans = ans + 1}\;\rightarrow\;\texttt{is ans ** 3 == x ?}$$

Whenever the candidates can be listed and there are few enough of them. The loop leaving is not the same as the loop succeeding, so the answer is only known after one more test.

Close enough, written as a window
$$\lvert \texttt{ans}^2 - x\rvert < \varepsilon\;\text{rather than}\;\texttt{ans}^2 = x$$

Every time the answer is not a whole number. The equality test on floats is the single most common never-ending loop in this chapter.

Bisection keeps the answer between two names
$$\texttt{low}\le\text{answer}\le\texttt{high},\;\texttt{ans}=(\texttt{low}+\texttt{high})/2$$

Any search where a guess that is too big tells you the answer is below it. Each pass throws away half of what is left, so the count of passes is small even for huge numbers.

The Newton step for a square root
$$\texttt{guess} \leftarrow \texttt{guess} - \frac{\texttt{guess}^2 - k}{2\,\texttt{guess}}$$

When the thing being solved is a polynomial and a starting guess is available. It is the fastest of the four here and the only one that can fail on a division by zero.

Three most common mistakes
  1. Reading the end of the loop as success. A guess-and-check loop also leaves when the candidates run out, so the line after it has to ask whether the last candidate actually passed the test.

  2. Testing two floats with ==. The loop while total != 1.0 with a step of 0.1 never stops, because the sum of ten tenths is 0.9999999999999999 and not 1.0.

  3. Bracketing a with high = x when x is below 1. The square root of 0.25 is 0.5, which is outside the interval, so the search converges on the wrong end and the epsilon test never passes.

Labs are 20 per cent of the mark on this course, the midterm 40 and the final 40. On the one past midterm paper available while this page was written, a single question made entirely of short programs to be traced by hand carried 30 of the 100 marks, and the programming questions on the same paper were graded on output that had to be exact. That is the reason so many questions here ask for characters rather than for a description.

How much time do you have?
10 minutes

The two things that break most answers on this chapter: a search loop has to be asked afterwards whether it succeeded, and floats are never compared with an equals sign. With these you can read any of the programs on the page without being surprised by the output.

The 60-second card · Listing the candidates and testing each one · Why the equality test is the bug, not the number · Formula card
45 minutes

Everything that becomes a program you could be asked to write: in both loop shapes, the epsilon and step pair, and bisection. This is the whole of the lab work for this week and most of a tracing question.

The 60-second card · Listing the candidates and testing each one · The same search as a counted loop, and how it says no · When no candidate is exactly right · Halving the interval instead of walking it · Why the equality test is the bug, not the number · Scaffolding comes off · B · computation
full read

Adds the parts that separate a program that runs from one that is right: what costs in passes, the measured counts for the four methods on the same number, why bisection needs the answer bracketed, and the Newton step with the two ways it blows up.

The 60-second card · Recall first · Conventions · Listing the candidates and testing each one · The same search as a counted loop, and how it says no · When no candidate is exactly right · Halving the interval instead of walking it · Why the equality test is the bug, not the number · Using the slope of the curve to choose the next guess · Scaffolding comes off · Full exam-style question · A · concept · B · computation · C · exam level · D · interleaved · Mistake ledger · Formula card · Check yourself
By the end of this section
  1. Write a guess-and-check loop for a whole-number answer, and write the test after the loop that decides between found and not found.

  2. Convert a search between a while loop and a counted loop with break, and give the value of the loop variable after either kind of ending.

  3. Choose an epsilon and a step for an approximate search, say how many passes that pair costs, and explain what happens when the step is larger than the window.

  4. Trace a bisection search pass by pass, keeping low, high and the midpoint in three columns, and state the it needs in order to work at all.

  5. Predict the printed value of a float computation, and replace an equality test between floats with a test that works.

  6. Apply the Newton step by hand for one or two rounds, and name the starting value that stops the program with an error.

Syllabus coverage

Simple numerical programs — covered

The four search methods of the chapter and the arithmetic they sit on: exhaustive enumeration, counted search with an early exit, approximate answers with an epsilon and a step, bisection search, the behaviour of floats, and the Newton-Raphson step.

Every part of the line has a block of its own: enumeration and its counted form in the first two, approximate answers and bisection in the next two, floats in the fifth and Newton-Raphson in the last.

Chapter 3 — covered

The textbook chapter behind the line. Its own order is enumeration, for loops, with bisection search, a few words about floats, and Newton-Raphson, and this page keeps that order.

The chapter's for-loop material was already needed a week earlier and was covered in the previous section, so here it appears as a recall and then only in the form the chapter uses it, a counted search that can stop early.

square roots with the ** operator — off syllabus

Getting a root straight from the exponent operator, which the chapter's own opening does not use.

Kept to three lines in the opening story, because it is the thing every reader tries first and because its wrong answer is what makes the rest of the page necessary. It is not part of the line and no question here depends on it.

Recall first
The three parts of a loop that ends

A name is set up before the loop, the condition reads that name, and the indented block changes it. Take away the third part and the same answer comes back for ever. Every search on this page is that shape with one extra line after it.

A search loop is judged by two things, whether it ends and what is true when it does, and both are decided by those three parts.

What a range call produces

range(a, b, c) gives a, then a plus c, and so on while the value is still below b. The stop value b never appears. To cover candidates 0 to n inclusive the call is range(0, n + 1).

A counted search that misses its last candidate reports not found for a number whose answer was the one it skipped.

Whole division and remainder

7 / 2 is 3.5 and its type is float. 7 // 2 is 3 and 7 % 2 is 1, both whole numbers. With a negative left side the floor moves away from zero: -7 // 2 is -4, not -3.

Divisor searches and digit-peeling loops on this page run on // and %, and mixing them with / turns a whole number into a float in the middle of a comparison.

abs and the two-sided distance

abs(v) is v without its sign. The whole chapter measures how wrong is this guess as abs(guess_value - target), which is one number whichever side the guess fell on.

Without abs each test needs two comparisons, and the epsilon window would have to be written twice.

Printing a float to a fixed number of places

format(v, '.4f') hands back the characters of v rounded to four places after the point. It changes the text, never the value, so a comparison written after it still sees the full number.

Answers on this page are long floats; the lab format asks for a fixed number of places, and format is on the closed-book cover sheet.

int and float conversion

int(v) on a float cuts the fraction off towards zero, so int(2.9) is 2 and int(-3.7) is -3. float(v) on a whole number gives the same value with a point. Neither rounds.

The opening failure of this page is exactly an int applied to a float that was a shade too small.

Try it yourself first (2 questions)
1§03.2 — what a range with a step actually produces

A short counted loop from the previous section, with a step that is not one.

s = 0
for i in range(2, 10, 3):
    s = s + i
    print(i, s)
print('sum', s)
Find(a) Write exactly what this prints.
Given
  • The call is range(2, 10, 3).

  • The running total starts at 0 and is printed after each addition.

IPython console
Hint 1/4

Settle the list of values the range produces before tracing anything, because everything else follows from it.

Hint 2/4

The values start at 2 and grow by 3 while they are still below 10, and the stop value itself never appears.

Hint 3/4

So the values are 2, 5 and 8, and the totals after each are 2, 7 and 15.

Hint 4/4

It prints three pairs and then sum 15.

Show solution

List the values

$$2,\ 5,\ 8$$

Start 2, step 3, and 11 would be the next one but it is not below the stop of 10.

$$\text{three passes}$$

One per value, so three printed lines from inside the loop.

Add them up as you go

$$0+2=2,\ 2+5=7,\ 7+8=15$$

The total is printed after the addition, so the first line already shows 2 rather than 0.

Answer $$\boxed{\texttt{2 2},\ \texttt{5 7},\ \texttt{8 15},\ \texttt{sum 15}}$$
Check

Check the total a second way: 2 plus 5 plus 8 is 15, and the average of the three is 5, the middle value, which is what an evenly spaced list should give.

2§03.1 — whole division, cutting, and fixed places

Five values printed on three lines, all from calls this page leans on.

print(7 // 2, 7 % 2)
print(int(3.99), abs(-4.5))
print(format(2 / 3, '.3f'))
Find(a) Write exactly what this prints.
Given
  • // is whole division and % is the remainder.

  • int on a float cuts the fraction off towards zero.

  • format(v, '.3f') hands back characters, rounded to three places.

IPython console
Hint 1/4

Three lines, five values. Take them one at a time and decide for each whether the result is a whole number or a float.

Hint 2/4

Whole division of whole numbers gives a whole number; abs removes a sign and keeps the type; format returns characters and never changes a value.

Hint 3/4

With 7 and 2: the whole division is 3 and the remainder is 1. int(3.99) cuts to 3, abs(-4.5) is 4.5, and two divided by three is 0.666..., which to three places is 0.667.

Hint 4/4

It prints 3 1, then 3 4.5, then 0.667.

Show solution

Do the two whole-number operations

$$\texttt{7 // 2}=3,\ \texttt{7 \% 2}=1$$

Both stay whole because both sides are whole. Using / here would have given 3.5 and changed the type.

$$\texttt{int(3.99)}=3$$

Cutting, not rounding, which is the trap.

Handle the sign and the formatting

$$\texttt{abs(-4.5)}=4.5$$

The sign goes, the fraction stays, so the value is still a float and prints with its point.

$$\texttt{format(2 / 3, '.3f')}=\texttt{0.667}$$

Three places, rounded on the way to characters. The value two thirds is untouched.

Answer $$\boxed{\texttt{3 1},\ \texttt{3 4.5},\ \texttt{0.667}}$$
Check

Check the remainder against the division: 2 times 3 plus 1 is 7, which is what // and % together always promise for whole numbers.

Notation
symbolreads asmeanswatch out
$\texttt{ans}$

ans, the current guess

The candidate being tested right now. On this page it always holds a guess, never the final answer, until the loop has left and the test after it has passed.

After a counted loop this name survives with the last value it took, which is why the test after the loop can read it at all.

$\varepsilon,\ \texttt{epsilon}$

epsilon

How close counts as close enough. A guess is accepted when the distance between what it gives and what was wanted is below this number.

It is a choice, not a property of the problem. Smaller epsilon means a better answer and more passes, and below about 1e-16 relative to the numbers involved no epsilon can be reached at all.

$\texttt{step}$

step

The gap between one candidate and the next in an exhaustive search over floats.

Unrelated to epsilon, and the two are easy to confuse. If step is larger than the window that epsilon opens, the search can jump over every acceptable candidate and report failure.

$\texttt{low},\ \texttt{high}$

low and high

The two ends of the interval that still contains the answer in a bisection search. Everything outside has been ruled out.

They have to contain the answer before the loop starts. Nothing in the code checks that, and a wrong bracket looks exactly like a slow search.

$\lvert a-b\rvert<\varepsilon$

the distance from a to b is below epsilon

Written in Python as abs(a - b) < epsilon. The one test that replaces a == b whenever a or b is a float.

The loop condition is the opposite of the acceptance test, so it is written with >=. Writing != there is the classic loop that never ends.

$\texttt{//},\ \texttt{\%}$

whole division, and remainder

a // b is how many whole times b fits in a, and a % b is what is left over. Both stay whole when both sides are whole.

a % b == 0 is the way to ask does b divide a, and it is not the same question as a / b == int(a / b), which goes through a float and can answer wrongly for big numbers.

$\texttt{x ** 0.5}$

x to the power a half

The square root of x, computed by the exponent operator.

It gives a float with a of its own, which is the failure this page opens with. It is also not defined for a negative x in the way you would expect, so nothing on this page relies on it.

Conventions used here
Every output on this page came from running the program.

No block of output here was predicted by eye. Each program was run and the characters it wrote were copied in, which is why several of them are ugly: an answer that prints as 4.999000000001688, a step that prints as 1e-05, a program that stops with an error message on its last line. Where a program cannot finish, its error type is shown as the last line of its output.

A chapter whose whole point is that approximate answers are exactly reportable cannot itself be rounding things off quietly.

Floats are printed as Python prints them, unless format is used.

Python shows the shortest text that reads back as the same value, so 0.1 prints as 0.1 even though what is stored is slightly more than a tenth. On this page a float is left in that form when the point is the value, and wrapped in format(v, '.4f') when the point is a report. When the stored value itself matters, it is shown at twenty places so the error is visible.

Half the surprises in this chapter come from the difference between what is stored and what is shown.

How close is close enough is always a named number.

Every approximate search here has a name epsilon for the size of the acceptance window and, where the candidates are spaced out, a name step for the gap between them. Neither is written as a bare number inside the condition, so both can be changed in one place and the cost of changing them can be measured.

The exercises ask what happens when one of the two changes, and that question is unanswerable in a program where the number is buried in the test.

What this page is allowed to use, and what it waits for.

Everything here is built from what the course has covered by the end of this week: numbers, text, True and False, input, print, if, elif, else, while, for, range, break, abs, int, float, str, format, round, and the string operations from the previous section. Functions come next, and modules, files, lists and dictionaries after that, so none of them appears here, and no program on this page imports anything.

A solution that uses a tool from a later week is not a solution you could have written this week, and the lab for this material is graded on what has been taught.

3.1Listing the candidates and testing each one

When no formula hands over the answer, generate the candidates in order and test each until one passes.

Last week a loop repeated work. The same shape can search, and that one change is the chapter.

Solvable with what we have
  • Compute with any operator, including **, and print the result.

  • Read a number from the keyboard and convert it.

  • Repeat a block while a condition holds, and count the passes.

Not solvable yet
  • Get the whole-number cube root of a number, or say it has none.

  • Get a square root that can be trusted digit for digit.

  • Find the smallest divisor of a number, which is a search and not a calculation.

The operator looks like enough for the first one:

print(729 ** (1 / 3))
print(int(729 ** (1 / 3)))
print(27 ** (1 / 3))

Sample Run:

8.999999999999998
8
3.0
Why it fails

The cube root of 729 is 9, and the first line falls short of it by two parts in a thousand million million. int cuts the fraction off and turns that shortfall into the wrong whole answer 8. The third line, where the same idea gives exactly 3.0, is what makes the method feel tested.

MethodMethod 3.1: exhaustive enumeration, or guess and check
Conditions
  • The set of candidates has to contain the answer. If it does not, the method cannot find it, and it will not say so unless you ask.

  • The candidates have to be generated in a fixed order, one per pass, so that every one is reached exactly once.

  • Each pass has to move to the next candidate. A pass that leaves the candidate unchanged makes the loop run for ever.

  • The loop leaving is not the same as the answer being found, so the line after the loop has to test the last candidate again.

$$\boxed{\texttt{ans = 0};\quad\texttt{while ans ** 3 < abs(x): ans = ans + 1};\quad\texttt{if ans ** 3 != abs(x): no answer}}$$

Start at the smallest candidate and, while it is still too small, move to the next. When the loop stops you are holding the first candidate that is not too small, which is either the answer or proof that there is none, so test it once more before reporting.

Looks like this, but is not

Start, condition, body: the same three parts.

x = 8
ans = 0
passes = 0
while ans ** 3 < abs(x) and passes < 4:
    print('testing', ans)
    passes = passes + 1
print('passes =', passes, 'ans =', ans)

Nothing moves to the next candidate, so the condition gets the same answer for ever. Without the counter cutting it off after four passes it would print testing 0 until killed. This is the method's third condition, and the one that fails silently.

testing 0
testing 0
testing 0
testing 0
passes = 4 ans = 0
passans at the start of the passans ** 3is it below 30what happens

1

0

0

yes

the body runs, ans becomes 1

2

1

1

yes

the body runs, ans becomes 2

3

2

8

yes

the body runs, ans becomes 3

4

3

27

yes, by three

the body runs, ans becomes 4

none

4

64

no

the loop leaves with ans at 4

The last row is not a pass. It is the test that fails, and it is the row that decides the answer: the loop hands over a candidate of 4, and because 64 is not 30 the report has to be not a perfect cube. A reader who only counts the passes gets four and concludes the answer is 4, which is the mistake the check below is built on.

The whole-number cube root of a negative number

Find the whole-number cube root of -64, or report that there is none. The search only knows how to count upwards, and -64 is negative, so the sign has to be dealt with separately.

x = -64
ans = 0
while ans ** 3 < abs(x):
    ans = ans + 1
if ans ** 3 != abs(x):
    print(x, 'is not a perfect cube')
else:
    if x < 0:
        ans = -ans
    print('Cube root of', x, 'is', ans)

Sample Run:

Cube root of -64 is -4
FindThe printed line, and where the sign is put back on.
Given
  • The number is -64.

  • Candidates count upwards from 0, so they are never negative.

  • The cube of a negative number is negative, so the cube root of a negative number is negative.

Solution

A while loop is the right shape here rather than a counted one, because the number of candidates to try is not known before the search begins; it depends on how big the number turns out to be.

Search on the size, not on the number

$$\texttt{while ans ** 3 < abs(x)}$$

Comparing against abs(x) keeps the search on positive candidates. Searching against x itself would fail at once, because 0 cubed is already greater than -64 and the loop would never run.

$$\texttt{ans} = 0,1,2,3,4$$

Four passes, since 3 cubed is 27 and still below 64.

Decide found or not found

$$\texttt{4 ** 3 == 64}$$

The candidate handed over by the loop passes the equality test, so this number does have a whole cube root.

$$\texttt{if x < 0: ans = -ans}$$

The sign is put back only after the answer is known. Doing it earlier would break the comparison the loop depends on.

Answer $$\boxed{\texttt{Cube root of -64 is -4}}$$
Check

Multiply back: -4 times -4 is 16, times -4 again is -64, and the sign is right because an odd power keeps the sign of its base.

Four passes for 64. For a number with n digits the count grows like the cube root of it, so 64 million would take 400 passes, which is still nothing.

Two habits come out of this one: search on a size and put the sign back at the end, and keep the found-or-not decision on a line of its own after the loop.

How many passes a search costs, counted by the program

The cost of guess and check is the number of passes, and the program can count them for you. Search for the whole-number square root of 1521 and report the count as well as the answer.

x = 1521
ans = 0
guesses = 0
while ans * ans < x:
    ans = ans + 1
    guesses = guesses + 1
print('ans =', ans)
print('guesses =', guesses)
print('exact square?', ans * ans == x)

Sample Run:

ans = 39
guesses = 39
exact square? True
FindThe answer, the number of passes, and the relation between them.
Given
  • The number is 1521.

  • Candidates are the whole numbers from 0 upwards.

  • A counter is set up before the loop and increased in the body.

Solution

Separate the two names

$$\texttt{ans}\;\text{and}\;\texttt{guesses}$$

One of them is the candidate and the other is the cost. They happen to end up equal here, and confusing them is the reason to keep both.

$$\texttt{ans * ans < x}$$

Written as a multiplication rather than ans ** 2, because at this point the two are the same and the multiplication is the cheaper thing to read.

Read the two numbers off the run

$$\texttt{ans = 39},\;\texttt{guesses = 39}$$

They agree because the counter starts at 0 and both advance once per pass, so the count is the candidate.

$$\texttt{39 * 39 = 1521}$$

The equality test after the loop passes, so 1521 is a perfect square and the report is exact rather than approximate.

Answer $$\boxed{\texttt{ans = 39},\;\texttt{guesses = 39},\;\texttt{True}}$$
Check

The cost has to be about the square root of the number, and the square root of 1521 is a shade under 40, so a count near 39 is the right order. A count of 1521 would mean the loop was stepping through the number itself.

39 passes for 1521. The same search on 1521 million would take about 39 thousand, which is the argument the next two blocks are built on.

Counting the passes inside the program is how every claim about cost on this page was produced. It is also the cheapest way to find out that a loop is not ending: the counter keeps climbing while nothing else changes.

Checkpoint
§03.1 — reading the last pass of a guess and check loop

A search prints its own progress. Nothing is typed in while it runs.

x = 30
ans = 0
while ans ** 3 < x:
    ans = ans + 1
    print(ans, ans ** 3)
print('stopped')
Find
  1. (a) Write exactly what this prints.

  2. (b) Say in one sentence why the last line printed is a cube above the target rather than below it.

Given
  • The target is 30 and candidates start at 0.

  • The body prints the new candidate and its cube on every pass.

  • The condition is tested before each pass, not after.

IPython console
Hint 1/4

You are asked for characters, so trace it: one row per pass, with the condition first and then the two things the body prints.

Hint 2/4

The body increases the candidate and then prints, so the first line printed is the cube of 1 and not of 0. The loop leaves when the cube is no longer below 30.

Hint 3/4

With a target of 30 the cubes tested are 1, 8, 27 and 64. The third pass prints 27, which is still below 30, so a fourth pass happens and prints 64.

Hint 4/4

It prints four numbered lines, then stopped, and the last of the four is 4 64.

Show solution

Decide the order inside one pass

$$\texttt{ans = ans + 1}\;\text{then}\;\texttt{print}$$

The increase is the first line of the body, so nothing is ever printed for the candidate the condition just tested. Getting this order wrong is what produces a spurious 0 0 first line.

$$\texttt{0 ** 3 < 30}\;\text{is}\;\texttt{True}$$

The condition holds at the start, which is the only reason the body runs at all.

Run the four passes

$$\texttt{1}\;\texttt{1}\quad\texttt{2}\;\texttt{8}$$

Two passes, and both cubes are far below 30, so the condition is still true.

$$\texttt{3}\;\texttt{27}$$

27 is below 30 by three, which is enough to buy a fourth pass; this is the row students drop.

$$\texttt{4}\;\texttt{64}$$

Now the condition fails on the next test and the loop leaves, with stopped printed after it.

Answer $$\boxed{\texttt{1 1},\ \texttt{2 8},\ \texttt{3 27},\ \texttt{4 64},\ \texttt{stopped}}$$
Check

Count the passes a different way: the loop runs once for each candidate whose cube is below 30, and those are 0, 1, 2 and 3, which is four passes. Four passes print four lines, which matches.

⚠ Treating the end of the loop as the answer

The loop is the part that looks like work, so it feels finished when it stops; and on a number that does have an exact answer the shortcut gives the right result, so it survives testing.

wrong$$\texttt{while ans ** 3 < x: ans = ans + 1};\;\texttt{print('root is', ans)}$$
right$$\texttt{if ans ** 3 != x: print('no whole root')};\;\texttt{else: print('root is', ans)}$$
⚠ Searching against a negative target

abs is easy to leave out because the search reads correctly without it, and the loop does not crash; it simply runs zero times and reports the candidate 0.

wrong$$\texttt{while ans ** 3 < x}\;\text{with}\;x=-64$$
right$$\texttt{while ans ** 3 < abs(x)}$$
⚠ Moving to the next candidate outside the loop body

The increase looks like part of the reporting rather than part of the search, and one level of indentation is all that separates the two.

wrong$$\texttt{while ans ** 3 < x:}\;\text{body holds only}\;\texttt{print(ans)}$$
right$$\texttt{while ans ** 3 < x:}\;\text{body holds}\;\texttt{print(ans)}\;\text{and}\;\texttt{ans = ans + 1}$$

A counted loop plus break searches the same set, and the code after it must tell an early exit from a range that ran out.

The search above decides for itself when to stop. Write the same search with a counted loop and the stopping moves into the range, which changes what has to be checked afterwards.

RuleRule 3.2: a counted search has two endings and one name
Conditions
  • The range has to cover every candidate. To include n the call is range(0, n + 1), because the stop value is never produced.

  • A break ends only the loop it sits in. In a loop inside a loop it ends the inner one and the outer one carries on.

  • The loop variable is still there after the loop, holding the last value it took, whether the loop broke out or ran to the end.

  • If the range produces nothing at all, the loop variable is never created, and reading it after the loop stops the program with a NameError.

$$\boxed{\texttt{for ans in range(0, n + 1): if test: break};\quad\texttt{then ask whether ans really passes}}$$

Walk the candidates in order and leave the loop the moment one passes. Afterwards the loop variable holds the candidate you stopped on, which is the winner if a break happened and merely the last one tried if the range ran out, so ask the question again outside the loop before reporting anything.

Looks like this, but is not

This looks like a finished search for a whole-number square root. It walks the candidates, it stops at the right place, and on a number that is a perfect square it prints the right answer.

target = 20
for ans in range(0, 5):
    if ans * ans == target:
        break
print('root is', ans)

For a target of 20 there is no whole-number root, so no break ever happens, the range simply runs out, and the loop variable is left holding 4. The program then announces that the root of 20 is 4. Nothing failed and nothing was reported as missing.

root is 4

Cube root of 8 as a counted search with an early exit

Rewrite the cube-root search as a counted loop. The textbook's own version of this uses the same trick: leave the loop at the first candidate that is big enough, then ask whether it is exactly right.

x = 8
for ans in range(0, abs(x) + 1):
    if ans ** 3 >= abs(x):
        break
if ans ** 3 != abs(x):
    print(x, 'is not a perfect cube')
else:
    print('Cube root of', x, 'is', ans)

Sample Run:

Cube root of 8 is 2
FindThe printed line, and why the exit test is not an equality.
Given
  • The number is 8.

  • Candidates run from 0 to the number itself, inclusive.

  • The exit test is >= rather than ==.

Solution

A counted loop is safe here only because the answer cannot be bigger than the number itself, so a finite range is known in advance. Where no such bound exists, this rewrite is not available and the while version is the only option.

Choose the range so no candidate is missed

$$\texttt{range(0, abs(x) + 1)}$$

The + 1 is there because the stop is never produced, and for x equal to 1 the only candidate is 1 itself, which the shorter call would leave out.

$$\texttt{abs(x)}$$

The same reason as before: candidates count upwards, so the target has to be a size.

Break on too big, not on equal

$$\texttt{if ans ** 3 >= abs(x): break}$$

Breaking on equality would walk the whole range when there is no answer, and worse, it would leave the loop variable at the top of the range instead of at the first candidate that .

$$\texttt{2 ** 3 = 8}$$

Here the first candidate that is not too small is exactly right, so the equality test after the loop passes.

Answer $$\boxed{\texttt{Cube root of 8 is 2}}$$
Check

Run the same shape on a number that is not a perfect cube and the report has to flip: for 10 the break happens at 3, 27 is not 10, and the program says so. That second run is the real test of this code, because the first one passes even when the check after the loop is missing.

Three passes for 8. The range would have allowed nine, so the break saved six tests; for a target of a million it saves almost all of them.

Two endings, one name. Whenever you see a loop variable being read after its loop, look for the test that decides which ending happened, and if it is not there the program is reporting a guess as an answer.

Smallest divisor of 91, and a flag that survives the loop

Find the smallest divisor of 91 above 1, or report that the number is prime. This is the search where the two endings really do mean opposite things, so the answer is carried in a name of its own rather than in the loop variable.

n = 91
smallest = 0
for d in range(2, n):
    if n % d == 0:
        smallest = d
        break
if smallest == 0:
    print(n, 'is prime')
else:
    print('smallest divisor of', n, 'is', smallest)
print('d ended at', d)

Sample Run:

smallest divisor of 91 is 7
d ended at 7
FindThe two printed lines, and why the extra name is worth it.
Given
  • The number is 91.

  • A divisor is a d with n % d == 0.

  • smallest starts at 0, a value no real divisor can have, so it doubles as nothing found yet.

Solution

Record the find in a separate name

$$\texttt{smallest = d};\;\texttt{break}$$

Written in that order so the name is set before the loop is left. Relying on the loop variable instead works here but breaks as soon as the range can run out, which is the counterexample above.

$$\texttt{smallest == 0}$$

A whole divisor is at least 2, so 0 is a value that cannot be confused with a real answer. This is the same idea as the -1 used for not found in the previous section.

Read the two endings off the run

$$\texttt{91 % 7 == 0}$$

The first divisor found is 7, because 91 is 7 times 13 and nothing below 7 divides it.

$$\texttt{d}\;\text{ends at}\;7$$

The loop variable stopped where the break fired, and it agrees with smallest only because a break did happen.

Answer $$\boxed{\texttt{smallest divisor of 91 is 7},\;\texttt{d ended at 7}}$$
Check

Divide back: 91 divided by 7 is 13 exactly, and no number from 2 to 6 divides 91, since it is odd, its digits sum to 10, it does not end in 0 or 5, and 91 divided by 6 is not whole. So 7 is the smallest.

Six passes for 91. The range allows 89, so the break is doing most of the work; a prime would use every one of the 89.

When the two endings mean opposite things, write the answer into a name inside the loop and read that name afterwards. The loop variable is a fact about the loop, not a result.

Checkpoint
§03.2 — what a counted loop leaves behind when it breaks

A counted loop with an early exit, and a report that reads the loop variable after the loop has finished.

total = 0
for i in range(1, 10, 3):
    if i * i > 20:
        break
    total = total + i
    print('i', i, 'total', total)
print('final', total, i)
Find
  1. (a) Write exactly what this prints.

  2. (b) Say what the last line would print if the break had never been reached.

Given
  • The range is range(1, 10, 3), so the candidates are 1, 4 and 7.

  • The loop leaves early as soon as the square of the candidate is above 20.

  • The last line prints the running total and the loop variable.

IPython console
Hint 1/4

The interesting part is the last line, so keep two columns while you trace: what the candidate is, and whether the body reached the addition or left before it.

Hint 2/4

A break leaves the loop immediately, so the lines after it in the body do not run for that pass. The loop variable keeps the value it had at that moment, and it still exists after the loop.

Hint 3/4

The candidates are 1, 4 and 7. For 1 and 4 the squares are 1 and 16, both below 20, so the total becomes 1 and then 5. For 7 the square is 49, so the break happens before the addition.

Hint 4/4

It prints two i ... total ... lines and then final 5 7.

Show solution

Write the candidates down before tracing

$$\texttt{range(1, 10, 3)}\;\rightarrow\;1,4,7$$

The step is 3 and the stop is 10, so 10 itself is not produced and the list has exactly three members. Settling this first stops the trace from inventing a fourth pass.

$$\texttt{1 * 1 = 1},\;\texttt{4 * 4 = 16}$$

Both below 20, so both passes reach the addition.

Handle the pass that leaves early

$$\texttt{7 * 7 = 49 > 20}$$

The test at the top of the body succeeds, so the break runs and the two lines under it are skipped for this pass and for ever.

$$\texttt{total = 5},\;\texttt{i = 7}$$

The total is what the first two passes built, and the loop variable is what the third pass was holding when it left.

Answer $$\boxed{\texttt{final 5 7}}$$
Check

Check the invariant separately: the total should be the sum of every candidate whose square is at most 20, and those are 1 and 4, so 5. The loop variable is not part of that sum, which is exactly why the two numbers on the last line disagree.

⚠ Reading the loop variable as though a break had happened

On the first test case there usually is an answer, so the break fires and the value is right. The bug only appears on the input that has no answer, which is exactly the case nobody tries.

wrong$$\texttt{for ans in range(0,5): if ans*ans==20: break};\;\texttt{print(ans)}$$
right$$\texttt{found = False};\;\texttt{if ans*ans==20: found = True; break};\;\texttt{if found: print(ans)}$$
⚠ A range that stops one candidate short

range(0, n) reads like the numbers up to n, and for most targets the answer is well inside the range, so the missing last candidate is never the one that mattered.

wrong$$\texttt{for ans in range(0, abs(x))}$$
right$$\texttt{for ans in range(0, abs(x) + 1)}$$
⚠ Expecting break to leave both loops

The word suggests leaving the whole search, and in a single loop that is what it does, so the meaning learned first is the one that is wrong in the nested case.

wrong$$\texttt{for a in ...: for b in ...: break}\;\Rightarrow\;\text{outer loop ends}$$
right$$\texttt{for a in ...: for b in ...: break}\;\Rightarrow\;\text{only the b loop ends}$$

3.3When no candidate is exactly right: epsilon and step

For an answer that is not a whole number, accept the first candidate that is close enough, and choose how close.

Every search so far compared whole numbers with an equals sign. Ask for the square root of 2 and no candidate will ever pass that test, so the test has to change.

RuleRule 3.3: an approximate search needs two numbers you choose
Conditions
  • epsilon says how close is close enough. It is a decision, not a property of the problem, and it belongs in a name of its own.

  • step says how far apart the candidates are. A smaller step means more passes, in direct proportion.

  • The step has to be small enough that at least one candidate lands inside the window epsilon opens, or the search steps over the answer and reports failure.

  • The loop needs a second way out for the case where the candidates have gone past the answer, or a failed search never ends.

  • The report after the loop has to say which of the two happened, exactly as in the whole-number searches.

$$\boxed{\text{accept ans when}\ \lvert \texttt{ans}^2 - x\rvert < \varepsilon,\ \text{step while}\ \texttt{ans}^2 \le x}$$

Walk candidates upwards in steps of the chosen size. Stop as soon as the square of the candidate is within epsilon of the target, or as soon as the square has gone past the target, and afterwards ask which of those two stopped the loop.

Looks like this, but is not

A window of 0.01 and a step of 0.1 both look small.

x = 2
epsilon = 0.01
step = 0.1
ans = 0.0
while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
print('stopped at ans =', ans)
if abs(ans ** 2 - x) >= epsilon:
    print('Failed on square root of', x)
else:
    print(ans, 'is close to the square root of', x)

The candidates are 1.4 and then 1.5, and the band that accepts a root of 2 lies between them. The second exit catches it, so the program reports failure rather than hanging, but the failure is the step's fault and not the problem's: with a step of 0.001 the same code succeeds.

stopped at ans = 1.5000000000000002
Failed on square root of 2
steppasses useddid it land in the window

0.1

50

yes

0.01

500

yes

0.001

4999

yes

0.0001

49990

yes

0.00001

499900

yes

Dividing the step by ten multiplies the passes by ten, exactly. This search buys accuracy at a fixed price per digit, and it is the reason the next block exists: bisection buys the same accuracy for about three passes per digit rather than ten times as many. Note also that every row lands in the window here, because 5 is the answer and every one of these steps divides into it.

Square root of 25 by stepping, and the cost of that

Find the square root of 25 to within 0.01 by stepping upwards, and have the program report how many passes it used.

x = 25
epsilon = 0.01
step = epsilon ** 2
guesses = 0
ans = 0.0
while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
    guesses = guesses + 1
print('guesses =', guesses)
if abs(ans ** 2 - x) >= epsilon:
    print('Failed on square root of', x)
else:
    print(ans, 'is close to the square root of', x)

Sample Run:

guesses = 49990
4.999000000001688 is close to the square root of 25
FindThe printed answer, the number of passes, and why the answer is not 5.0.
Given
  • The target is 25 and epsilon is 0.01.

  • The step is epsilon squared, which is 0.0001.

  • Candidates start at 0.0 and only ever increase.

Solution

Stepping is chosen here only to measure what it costs. For this particular target an exhaustive search over whole numbers would have found 5 in five passes, but that search cannot answer the same question for 24.

Write both exits into one condition

$$\texttt{while abs(ans ** 2 - x) >= epsilon and ans * ans <= x}$$

The left half keeps stepping while the guess is not good enough; the right half stops the loop if the guesses have climbed past the target. Without the right half a failed search runs for ever.

$$\texttt{step = epsilon ** 2}$$

A step far smaller than the window, which guarantees a candidate lands inside it. The price of that guarantee is the pass count below.

Read the answer and its error

$$\texttt{ans}=4.999000000001688$$

The first candidate whose square comes within 0.01 of 25. Its square is about 24.990001, which is 0.01 under the target, just inside the window.

$$\texttt{guesses}=49990$$

Stepping from 0 to about 5 in steps of 0.0001 takes about 50 thousand passes, and the program counted 49990 of them.

Explain the shape of the answer

$$\texttt{4.999} \ne \texttt{5.0}$$

The search accepts the first candidate that is close enough, and that happens just below the true root, so a stepping search from below always lands on the low side.

$$\texttt{...0001688}$$

The tail comes from adding 0.0001 fifty thousand times: each addition carries a tiny error and they accumulate, which is the subject of a later block on this page.

Answer $$\boxed{\texttt{guesses = 49990},\ \texttt{4.999000000001688}}$$
Check

Estimate the count without running anything: the answer is near 5, the step is 0.0001, so the count must be near 5 divided by 0.0001, which is 50 thousand. The printed 49990 is that number, ten short, and the ten short is the window.

49990 passes for one number. A program that did this for every number from 1 to 1000 would use about 50 million passes.

Two knobs, one price. Epsilon sets the quality of the answer and the step sets the bill, and in this method the bill grows as fast as the quality improves.

The same loop with a step of 0.1 is the middle row of the table two blocks down, and its answer is the one the next block is about.

x = 25
epsilon = 0.01
step = 0.1
guesses = 0
ans = 0.0
while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
    guesses = guesses + 1
print('guesses =', guesses)
print(ans, 'is close to the square root of', x)

Sample Run:

guesses = 50
4.999999999999998 is close to the square root of 25
Checkpoint
§03.3 — stopping inside the acceptance window

A stepping search for the square root of 10, started deliberately close to the answer so that the trace is short.

x = 10
epsilon = 0.1
ans = 3.0
while abs(ans ** 2 - x) >= epsilon:
    ans = ans + 0.01
print(format(ans, '.2f'))
print(format(ans ** 2, '.4f'))
print(abs(ans ** 2 - x) < epsilon)
Find
  1. (a) Write exactly what this prints.

  2. (b) Say which candidate is the first one accepted, and how far its square is from 10.

Given
  • The target is 10 and epsilon is 0.1.

  • The first candidate is 3.0 and the step is 0.01.

  • The answer is accepted when the square is within 0.1 of 10, which means the square is above 9.9.

IPython console
Hint 1/4

The question is where the loop stops, so turn the acceptance test into a statement about the square: the loop keeps going while the square is more than 0.1 away from 10.

Hint 2/4

Since every candidate here is below the square root of 10, its square is below 10, so the distance is 10 minus the square. The loop stops at the first candidate whose square is above 9.9.

Hint 3/4

Starting from 3.0 and stepping by 0.01: 3.14 squared is 9.8596, still too far; 3.15 squared is 9.9225, which is 0.0775 away from 10 and inside the window.

Hint 4/4

It stops at 3.15, and prints that, then 9.9225, then True.

Show solution

Turn the loop condition into a threshold

$$\lvert \texttt{ans}^2-10\rvert\ge 0.1$$

The loop continues while this holds. Since the candidates come from below, it is the same as asking whether the square is below 9.9, and one threshold is easier to trace than a distance.

$$\texttt{3.14}^2=9.8596$$

Below 9.9, so one more step is taken. This is the last rejected candidate.

Find the first candidate above the threshold

$$\texttt{3.15}^2=9.9225$$

Above 9.9, so the loop leaves. The distance from 10 is 0.0775, comfortably inside epsilon.

$$\texttt{format(ans, '.2f')}\rightarrow\texttt{3.15}$$

Two places is enough to show which candidate stopped the loop, and it hides an accumulated error that is not the point here.

Answer $$\boxed{\texttt{3.15},\ \texttt{9.9225},\ \texttt{True}}$$
Check

Check the window from the other side: the square root of 9.9 is about 3.1464 and the square root of 10.1 is about 3.1781, so any candidate between those two is accepted. 3.15 is inside that pair and 3.14 is not, which agrees with the trace.

⚠ Testing for equality instead of closeness

Every earlier search ended on an equals sign, and the habit carries over. The loop then never ends, and the program looks like it is working hard.

wrong$$\texttt{while ans ** 2 != x}$$
right$$\texttt{while abs(ans ** 2 - x) >= epsilon}$$
⚠ A step bigger than the window

Both numbers are small, so they feel interchangeable, and the two names are never compared in the code. The search then reports failure on a problem that has an answer.

wrong$$\varepsilon=0.01,\ \texttt{step}=0.1\ \Rightarrow\ \text{no candidate inside}$$
right$$\texttt{step}\ \text{below the window, for example}\ \varepsilon^2$$
⚠ Leaving out the second exit

With a small enough step the search does succeed, so the missing exit is invisible until the day the step or the target changes.

wrong$$\texttt{while abs(ans ** 2 - x) >= epsilon}$$
right$$\texttt{while abs(ans ** 2 - x) >= epsilon and ans * ans <= x}$$

3.4Halving the interval instead of walking it

Keep the answer between two names and test the midpoint; each pass throws away half of what is left.

The stepping search asks fifty thousand questions and throws away almost every answer. One question, asked in the middle, rules out half the candidates at once.

RuleRule 3.4: bisection search
Conditions
  • The answer has to be between low and high before the loop starts. Nothing in the code checks this, and a wrong bracket looks exactly like a search that will not converge.

  • A guess that is too big has to mean the answer is below it. That is true for squares of positive numbers, which is why this works here at all.

  • The guess is the midpoint, and after the test one of the two ends moves to it, so the interval halves on every pass.

  • For a target below 1 the square root is above the target, so the bracket has to be widened to 1. This is the one special case that catches everybody.

$$\boxed{\texttt{ans} = \frac{\texttt{low}+\texttt{high}}{2};\quad \texttt{ans}^2 < x \Rightarrow \texttt{low} = \texttt{ans};\quad \text{else}\ \texttt{high} = \texttt{ans}}$$

Guess the middle of the interval. If its square is too small the answer is in the upper half, so pull the low end up to the guess; otherwise the answer is in the lower half, so pull the high end down. Repeat until the guess is close enough, and the interval you are left with still contains the answer.

Looks like this, but is not

The textbook code, used on a number below 1.

x = 0.25
epsilon = 0.01
low = 0.0
high = x
ans = (high + low) / 2.0
guesses = 0
while abs(ans ** 2 - x) >= epsilon and guesses < 30:
    guesses = guesses + 1
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
print('guesses =', guesses)
print('ans =', ans)
print('close enough?', abs(ans ** 2 - x) < epsilon)

The square root of 0.25 is 0.5, which is above 0.25, so the answer was never inside the interval. Every pass finds the midpoint too small, pulls the low end up, and creeps towards 0.25 for ever. Only the pass limit stops it, and the epsilon test never passes.

guesses = 30
ans = 0.24999999988358468
close enough? False
methodpassesanswer printed

stepping by 0.0001

49990

4.999000000001688

stepping by 0.1

50

4.999999999999998

bisection

13

5.00030517578125

Bisection wins on passes by a factor of about four thousand against the fine step, and it is the only one of the three whose count barely moves when the target grows: for a target of twelve million it needs 37 passes, while the same fine step would need about 35 million. The middle row is a reminder that a coarse step can be lucky: fifty tenths add up to 4.999999999999998 rather than 5, and that is close enough for the window to accept it.

Square root of 25 by bisection, with the bracket guarded

Find the square root of 25 to within 0.01 by halving, and guard the bracket so that the same program also works for a target below 1.

x = 25
epsilon = 0.01
low = 0.0
if x < 1:
    high = 1.0
else:
    high = x
ans = (high + low) / 2.0
guesses = 0
while abs(ans ** 2 - x) >= epsilon:
    guesses = guesses + 1
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
print('guesses =', guesses)
print(ans, 'is close to the square root of', x)

Sample Run:

guesses = 13
5.00030517578125 is close to the square root of 25
FindThe number of passes, the printed answer, and why the guard is there.
Given
  • The target is 25 and epsilon is 0.01.

  • low starts at 0.0, and high starts at the target, or at 1.0 when the target is smaller than 1.

  • The guess is always the midpoint of the current interval.

Solution

Bisection is chosen over stepping because the answer is bracketed and the test is one-directional: a guess that is too big proves the answer is smaller. Without that second property, halving would have nothing to throw away.

Set a bracket that really contains the answer

$$\texttt{low = 0.0}$$

No square root of a positive number is negative, so zero is a safe lower end.

$$\texttt{if x < 1: high = 1.0 else: high = x}$$

For x at least 1 the root is at most x, so x is a safe upper end. Below 1 that fails, since the root of 0.25 is 0.5, and 1.0 is then the safe choice.

Halve until the guess is close enough

$$\texttt{ans = (high + low) / 2.0}$$

Recomputed at the end of every pass, after one of the ends has moved. Computing it before the move would test the same guess twice.

$$\texttt{if ans ** 2 < x: low = ans else: high = ans}$$

The half that cannot contain the answer is discarded. Swapping these two lines is the single most common bisection bug, and it makes the interval crawl one way instead of closing.

Read the result

$$\texttt{guesses = 13}$$

Thirteen halvings take an interval of width 25 down to about 0.003, which is fine enough for the epsilon test to pass.

$$\texttt{5.00030517578125}$$

The answer is above 5 this time, not below it. A bisection approaches from whichever side the last halving left it on, unlike a stepping search which always comes from below.

Answer $$\boxed{\texttt{guesses = 13},\ \texttt{5.00030517578125}}$$
Check

Check the count against the width instead of rerunning. Near 5 the square moves ten times as fast as the root, so the guess has to land within 0.001 of 5. Halving 25 thirteen times leaves a width of 0.003, whose midpoint is at most 0.0015 from the answer, so thirteen or fourteen is the right order; this run landed 0.0003 away.

13 passes, against 49990 for the fine stepping search and 4999 for a step of 0.001.

Bisection is the first method on this page whose cost grows with the number of digits wanted rather than with the size of the answer. That is why the pass count hardly changes when the target jumps from 25 to twelve million.

Every pass of the search for the square root of 10, printed

Print the interval at the start of every pass, so that the halving can be watched. This run is the one drawn in the figure above.

x = 10
epsilon = 0.01
low = 0.0
high = x
ans = (high + low) / 2.0
guesses = 0
while abs(ans ** 2 - x) >= epsilon:
    print('low', low, 'high', high, 'ans', ans)
    guesses = guesses + 1
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
print('after', guesses, 'halvings ans =', ans)

Sample Run:

low 0.0 high 10 ans 5.0
low 0.0 high 5.0 ans 2.5
low 2.5 high 5.0 ans 3.75
low 2.5 high 3.75 ans 3.125
low 3.125 high 3.75 ans 3.4375
low 3.125 high 3.4375 ans 3.28125
low 3.125 high 3.28125 ans 3.203125
low 3.125 high 3.203125 ans 3.1640625
low 3.125 high 3.1640625 ans 3.14453125
low 3.14453125 high 3.1640625 ans 3.154296875
low 3.154296875 high 3.1640625 ans 3.1591796875
after 11 halvings ans = 3.16162109375
FindHow the interval closes, and how many passes it takes.
Given
  • The target is 10 and epsilon is 0.01.

  • The bracket is 0.0 to 10, which does contain the answer of about 3.1623.

  • Each line shows low, high and the midpoint before the test is applied.

Solution

Watch which end moves

$$\texttt{low 0.0 high 10 ans 5.0}$$

25 is above 10, so the high end comes down to 5 and the second line starts from 0 to 5.

$$\texttt{low 2.5 high 5.0 ans 3.75}$$

Now 6.25 is below 10, so the low end went up on the previous pass, and 14.06 is above 10, so the high end comes down on this one. The ends take turns, and they do not alternate in any pattern worth remembering.

Watch the width

$$10 \rightarrow 5 \rightarrow 2.5 \rightarrow 1.25$$

The width is exactly halved by every pass, no matter which end moves, because the midpoint always splits it evenly.

$$\texttt{11}\ \text{halvings}$$

After eleven passes the width is 10 divided by 2048, which is about 0.005, and the epsilon test on the squares finally passes.

Answer $$\boxed{\texttt{after 11 halvings ans = 3.16162109375}}$$
Check

The true root is 3.16227766, so the answer is wrong by about 0.0007. The epsilon test was on the squares, and 3.16162 squared is 9.99584, which is 0.004 from 10 and inside the window, so both numbers are consistent with an epsilon of 0.01.

11 passes for a target of 10, against 13 for 25 and 37 for twelve million.

Printing the interval on every pass is the fastest way to find a bisection bug: if one end never moves, the bracket was wrong, and if the width does not halve, the midpoint is being computed at the wrong moment.

Checkpoint
§03.4 — three passes of an interval halving

Three passes of a bisection, written with a counted loop so that the number of passes is fixed rather than decided by an epsilon.

low = 0.0
high = 8.0
for i in range(3):
    mid = (low + high) / 2
    print(i, low, high, mid)
    if mid * mid < 8:
        low = mid
    else:
        high = mid
print(format(low, '.3f'), format(high, '.3f'))
Find
  1. (a) Write exactly what this prints.

  2. (b) Give the width of the interval after the third pass, and say how many more passes would bring it below 0.1.

Given
  • The interval starts at low 0.0 and high 8.0.

  • The target for the comparison is 8, so the loop is looking for the square root of 8, which is about 2.8284.

  • Each pass prints the pass number and the three values as they are at the start of that pass.

IPython console
Hint 1/4

The printing happens before the update, so each line shows the interval the pass inherited. Trace with three columns and fill a row in before you apply the test.

Hint 2/4

The midpoint is the average of the two ends. If its square is below the target the answer is above it, so the low end moves up; otherwise the high end moves down.

Hint 3/4

Starting from 0 and 8: the midpoint is 4, and 16 is above 8, so high becomes 4. Then the midpoint is 2, and 4 is below 8, so low becomes 2. Then the midpoint is 3.

Hint 4/4

It prints three trace lines and then 2.000 3.000.

Show solution

Do the first pass on paper

$$\texttt{mid}=(0+8)/2=4$$

The midpoint of the whole interval. The print happens before any update, so the first line still shows 0.0 and 8.0.

$$\texttt{16 < 8}\ \text{is}\ \texttt{False}$$

So the answer is below 4 and the high end comes down to 4. Keeping the direction straight here is the whole trick.

Finish the remaining two passes

$$\texttt{mid}=(0+4)/2=2,\ \texttt{4 < 8}$$

True this time, so the low end goes up to 2 and the interval is now 2 to 4.

$$\texttt{mid}=(2+4)/2=3,\ \texttt{9 < 8}$$

False, so the high end comes down to 3 and the loop has run out of passes with the interval at 2 to 3.

Turn the width into a count

$$8 \rightarrow 4 \rightarrow 2 \rightarrow 1$$

Each pass halves the width, whichever way the test goes, so the width after three passes is 8 divided by 8.

$$1 \cdot (1/2)^4 = 0.0625$$

Four more halvings take the width from 1 to 0.0625, and three would only reach 0.125, which is still above 0.1.

Answer $$\boxed{\texttt{2.000 3.000},\ \text{width}\ 1,\ \text{four more passes}}$$
Check

Check the bracket holds: 2 squared is 4 and 3 squared is 9, and the target 8 lies between them, so the answer really is inside the final interval. If the target had fallen outside, the trace would have gone wrong at some earlier pass.

⚠ Bracketing with high = x when x is below 1

For every target above 1 the root is below the target, so the bracket looks obviously right, and nobody tests a fraction until the lab does.

wrong$$\texttt{high = x}\ \text{with}\ x=0.25$$
right$$\texttt{if x < 1: high = 1.0 else: high = x}$$
⚠ Moving the wrong end

The two lines look symmetrical, and the condition reads the guess is too small, which is easy to attach to the wrong end in a hurry.

wrong$$\texttt{if ans ** 2 < x: high = ans}$$
right$$\texttt{if ans ** 2 < x: low = ans}$$
⚠ Recomputing the midpoint before moving an end

The midpoint line looks like setup, so it drifts to the top of the loop body, where it computes the same value the pass has just tested.

wrong$$\texttt{while ...: ans = (low+high)/2;\ test;\ move end}\ \text{with the guess stale}$$
right$$\texttt{while ...: test;\ move end;\ ans = (low+high)/2}$$

3.5Why the equality test is the bug, not the number

Floats are binary fractions, so most decimals are stored slightly wrong, and equality between them is a coin toss.

Both searches above ended with a distance and an epsilon rather than an equals sign. That was not caution about method; it is forced by how the numbers are stored.

NoteRule 3.5: never compare two floats with ==
Conditions
  • A float is stored as a binary fraction with a fixed number of digits, so a decimal such as 0.1 has no exact form and is kept as the nearest available value.

  • Arithmetic on those values rounds to the nearest available value again, so errors accumulate as a loop runs.

  • Printing hides this. Python shows the shortest text that reads back as the same value, so a stored value a hair off 0.1 still prints as 0.1.

  • The replacement for a == b is abs(a - b) < epsilon, with an epsilon chosen for the job, and the loop condition is written with >= because it is the opposite of acceptance.

  • Whole numbers are exact, so == between two ints is fine and is used freely on this page.

$$\boxed{\texttt{a == b}\ \text{for floats}\ \longrightarrow\ \texttt{abs(a - b) < epsilon}}$$

Two floats that should be equal usually differ in their last few digits, so asking whether they are the same value is asking the wrong question. Ask instead whether the distance between them is smaller than an amount you are willing to ignore.

Looks like this, but is not

Ten passes, a tenth each, stop at one. It even has a guard.

value = 0.0
steps = 0
while value != 1.0 and steps < 12:
    value = value + 0.1
    steps = steps + 1
print('steps =', steps)
print('value =', value)
print('value == 1.0 ?', value == 1.0)

On the tenth pass the total is 0.9999999999999999, so the equality test fails, and on the eleventh it is past 1.0 and can never come back. Only the pass guard ends it, at a total of 1.2. Without that guard this is an endless loop whose condition looks obviously reachable.

steps = 12
value = 1.2
value == 1.0 ? False
written asstored value at twenty placesexact

0.5

0.50000000000000000000

yes, a half is a binary fraction

0.1

0.10000000000000000555

no, slightly high

0.1 + 0.2

0.30000000000000004441

no, one step above 0.3

The third row is the whole lesson. The error is in the seventeenth place, which no report on this course will ever show, and it is still enough to make 0.1 + 0.2 == 0.3 false. A test that has to survive that needs a window, and 0.0001 is a window that swallows an error of this size ten thousand million times over.

What 0.1 + 0.2 is, and what a sum of ten tenths is

Print the sum of a tenth and two tenths, then build 1.0 out of ten tenths in a loop, and compare both with the value they should be.

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
total = 0.0
for i in range(10):
    total = total + 0.1
print(total)
print(total == 1.0)
print(abs(total - 1.0) < 0.0001)

Sample Run:

0.30000000000000004
False
0.9999999999999999
False
True
FindThe five printed lines, and which comparison is the useful one.
Given
  • The two additions are done in floats, the only kind of number with a decimal point in this course.

  • The loop runs exactly ten times.

  • The last line uses a window of 0.0001 instead of equality.

Solution

Read the first pair

$$\texttt{0.1 + 0.2}\rightarrow\texttt{0.30000000000000004}$$

Python prints the shortest text that reads back as the stored value, and here that text is long, which is itself the signal that the value is not 0.3.

$$\texttt{0.1 + 0.2 == 0.3}\rightarrow\texttt{False}$$

The two stored values are one step apart, so the equality test is answering correctly. The question was wrong, not the answer.

Read the loop's total

$$\texttt{total}\rightarrow\texttt{0.9999999999999999}$$

Ten additions, each rounding to the nearest available value, and the errors do not cancel. Note it lands below 1.0 here while the first sum landed above 0.3: the direction is not predictable by eye.

$$\texttt{abs(total - 1.0) < 0.0001}\rightarrow\texttt{True}$$

The same total, a different question, and now a usable answer. This is the line every loop on this page is built on.

Answer $$\boxed{\texttt{0.30000000000000004},\ \texttt{False},\ \texttt{0.9999999999999999},\ \texttt{False},\ \texttt{True}}$$
Check

Check that the trouble is the decimal and not the loop: replace 0.1 with 0.25 and four passes give exactly 1.0, with the equality test passing. Same loop, same arithmetic, exact step, so the loop was never the problem.

Ten additions are enough to move the error into the sixteenth place. The stepping search earlier did fifty thousand of them, which is why its answer ended in 0001688.

Two floats that ought to be equal are compared with a window, always. The only comparisons on this page that use == are between whole numbers.

int, round and format: three ways to shorten a number

The three calls that turn a long float into something short do three different things, and only one of them changes the value.

print(int(2.9))
print(round(2.5))
print(round(3.5))
print(round(2.675, 2))
print(format(2.675, '.2f'))

Sample Run:

2
2
4
2.67
2.67
FindThe five printed lines, and why two of them disagree with school rounding.
Given
  • int cuts the fraction off towards zero.

  • round with no second argument goes to the nearest whole number, and a value exactly halfway goes to the even one.

  • format hands back characters and leaves the value alone.

Solution

Separate cutting from rounding

$$\texttt{int(2.9)}\rightarrow\texttt{2}$$

Cutting, not rounding. This is the call that turned 8.999 into 8 in the story at the top of this page.

$$\texttt{round(2.5)}\rightarrow\texttt{2},\ \texttt{round(3.5)}\rightarrow\texttt{4}$$

Halfway values go to the even neighbour, so the two answers go in opposite directions. Over many values this keeps sums from drifting upwards.

Watch the stored value defeat the rounding

$$\texttt{round(2.675, 2)}\rightarrow\texttt{2.67}$$

School rounding says 2.68. The stored value of 2.675 is a hair below the halfway point, so the nearest two-place value really is 2.67.

$$\texttt{format(2.675, '.2f')}\rightarrow\texttt{2.67}$$

The same reason, and the same answer, which is worth knowing before an exercise asks for two decimal places.

Answer $$\boxed{\texttt{2},\ \texttt{2},\ \texttt{4},\ \texttt{2.67},\ \texttt{2.67}}$$
Check

Ask for more places to see the cause: printing 2.675 at twenty places shows 2.67499999999999982236, which is below the halfway point, so rounding down is the correct nearest answer rather than a bug.

When an exercise asks for a fixed number of places, use format and expect the last place to disagree with hand arithmetic once in a while. When it asks for a whole number, decide on purpose whether you mean cutting or rounding.

Checkpoint
§03.5 — two loops, one step that lands and one that does not

Two loops, the same target of 1.0, different steps and different tests.

step = 0.25
total = 0.0
count = 0
while total != 1.0:
    total = total + step
    count = count + 1
print(count, total)
step = 0.1
total = 0.0
count = 0
while abs(total - 1.0) > 0.0001:
    total = total + step
    count = count + 1
print(count, format(total, '.4f'))
Find
  1. (a) Write exactly what this prints.

  2. (b) Say what would happen if the second loop used the same equality test as the first.

Given
  • The first loop adds 0.25 each pass and leaves when the total is exactly 1.0.

  • The second adds 0.1 each pass and leaves when the total is within 0.0001 of 1.0.

  • Both print a pass count, and the second prints its total to four places.

IPython console
Hint 1/4

The two loops differ in two ways at once, so decide first which difference matters: the step, or the test. Ask which of 0.25 and 0.1 can be stored exactly.

Hint 2/4

A quarter is a half of a half, so it is stored exactly and four of them add to exactly 1.0. A tenth is not a fraction of that kind, so ten of them add to something a hair under 1.0.

Hint 3/4

Adding 0.1 ten times gives 0.9999999999999999, whose distance from 1.0 is about 1.1e-16, which is far below the 0.0001 window, so that loop leaves after ten passes.

Hint 4/4

It prints 4 1.0 and then 10 1.0000.

Show solution

Sort the two steps by whether they are exact

$$0.25 = 1/4 = 2^{-2}$$

A sum of halves of halves, which a binary fraction stores exactly. Four of them are exactly 1.0, so nothing is lost.

$$0.1\ \text{is not}\ 2^{-k}\ \text{or a sum of a few}$$

Its stored value is a shade above a tenth, and ten of those add to a shade below 1.0. This asymmetry is not a rule to memorise, only a warning that the sum is not the target.

Count the passes for each loop

$$4 \times 0.25 = 1.0$$

The equality test passes on the fourth pass, so the count printed is 4 and the total prints as 1.0.

$$10 \times 0.1 = 0.9999999999999999$$

The distance from 1.0 is about 1.1e-16, well inside the 0.0001 window, so the second loop also leaves after ten passes.

Read what format does to the second total

$$\texttt{format(total, '.4f')}\rightarrow\texttt{1.0000}$$

Four places cannot show an error in the sixteenth place, so the output looks like a clean 1.0 even though the stored value is not.

Answer $$\boxed{\texttt{4 1.0},\ \texttt{10 1.0000}}$$
Check

Check the claim about tenths on its own: printing the sum of ten 0.1 values without format gives 0.9999999999999999, and comparing it with 1.0 gives False. If that were not so, the second loop's window would be pointless.

⚠ Ending a loop on equality between floats

The target looks reachable, and with an exact step such as 0.25 it really is, so the habit survives every test until the step becomes a tenth.

wrong$$\texttt{while total != 1.0}$$
right$$\texttt{while abs(total - 1.0) >= 0.0001}$$
⚠ Believing the printed value is the stored value

Python prints 0.1 for a value that is not a tenth, which is helpful almost always and misleading exactly when the comparison matters.

wrong$$\texttt{print(0.1)}\rightarrow\texttt{0.1}\ \Rightarrow\ \text{stored value is a tenth}$$
right$$\texttt{format(0.1, '.20f')}\rightarrow\texttt{0.10000000000000000555}$$
⚠ Using int where rounding was meant

Both shorten a number, and on a value like 8.999 the two differ by a whole unit, which is exactly where marks are lost.

wrong$$\texttt{int(8.999999999999998)}\rightarrow\texttt{8}$$
right$$\texttt{round(8.999999999999998)}\rightarrow\texttt{9}$$

3.6Using the slope of the curve to choose the next guess

Turn the current guess into a better one with arithmetic instead of searching, and repeat until it is close enough.

Bisection ignores everything about a guess except whether it was too big. The size of the miss is information, and using it turns thirteen passes into four.

RuleRule 3.6: the Newton-Raphson step for a square root
Conditions
  • The quantity being solved has to be written as a height that should be zero. For a square root of k that height is the guess squared minus k.

  • The step needs the slope of that height. For the guess squared minus k the slope is twice the guess, which is the only piece of calculus this page uses and it is quoted, not derived.

  • The starting guess must not be zero, because the slope there is zero and the program stops on a division by zero.

  • Nothing brackets the answer, so there is no interval to watch. The only sign of progress is the height falling towards zero.

  • For a square root of a positive number and a positive start the steps do close in. That is not true of the method in general, and the textbook is careful to say so.

$$\boxed{\texttt{guess} \leftarrow \texttt{guess} - \frac{\texttt{guess}^2 - k}{2\,\texttt{guess}}}$$

Take how far the square of the guess is from the target, divide it by twice the guess, and subtract that from the guess. Repeat until the square of the guess is within epsilon of the target.

Looks like this, but is not

Zero looks like a harmless first guess, and it is the value every earlier search on this page starts from.

k = 24.0
epsilon = 0.01
guess = 0.0
while abs(guess * guess - k) >= epsilon:
    guess = guess - (((guess ** 2) - k) / (2 * guess))
print('Square root of', k, 'is about', guess)

The step divides by twice the guess, and twice zero is zero, so the first pass stops the program. The earlier searches could start at zero because they only ever added to the guess; this one divides by it.

ZeroDivisionError: float division by zero
stepguessguess squared minus 24

start

12.0

120.0

1

7.0

25.0

2

5.214285714285714

3.1887755102040813

3

4.908512720156556

0.09349712393871101

4

4.8989887432139305

under the epsilon of 0.01

The height falls from 120 to 25 to 3.19 to 0.093, so each step divides the miss by roughly the previous miss rather than by a fixed factor. Bisection would have divided the interval by two each time and needed nine passes for the same target, which is the comparison the last worked example measures directly.

Square root of 24 in four steps

Find the square root of 24 to within 0.01 with the Newton step, starting from half the target, and count the steps.

k = 24.0
epsilon = 0.01
guess = k / 2.0
guesses = 0
while abs(guess * guess - k) >= epsilon:
    guesses = guesses + 1
    guess = guess - (((guess ** 2) - k) / (2 * guess))
print('guesses =', guesses)
print('Square root of', k, 'is about', guess)

Sample Run:

guesses = 4
Square root of 24.0 is about 4.8989887432139305
FindThe step count, the answer, and why the start is not important.
Given
  • The target is 24.0 and epsilon is 0.01.

  • The starting guess is half the target, which is 12.0.

  • The loop condition measures the height of the curve, not the change in the guess.

Solution

Newton is chosen when the height and its slope are both easy to write. For a square root they are, so there is no reason to halve intervals. For a problem where the slope is unknown, bisection is still the method that works.

Start anywhere positive

$$\texttt{guess = k / 2.0}$$

Half the target is a convenient start with no special meaning. Starting at 24 or at 1 gives the same answer in five or six steps instead of four.

$$\texttt{guess}\ne 0$$

The only forbidden start, because the step divides by twice the guess.

Repeat the step until the height is small

$$\texttt{while abs(guess * guess - k) >= epsilon}$$

The same window as everywhere else on this page, measured on the square rather than on the root, which matches how the textbook writes it.

$$\texttt{guess = guess - ((guess ** 2 - k) / (2 * guess))}$$

One line, one step. The brackets matter: without them the division would take only part of the numerator.

Answer $$\boxed{\texttt{guesses = 4},\ \texttt{4.8989887432139305}}$$
Check

Square the answer: 4.8989887432139305 squared is 24.000000000000004 to the places that matter, so the answer is right to fifteen digits, far better than the epsilon of 0.01 demanded. That overshooting of the requirement is itself typical of the method.

4 steps, against 13 halvings and 49990 fine steps for the comparable target of 25.

The pattern to carry away is that the answer is far more accurate than the epsilon asked for. With this method the last step usually jumps from a few correct digits to all of them, so a loose epsilon costs nothing.

The same target by halving and by the Newton step, counted

Put the two methods in one program on a target big enough to separate them, 12345678, and print both counts and both answers.

k = 12345678.0
epsilon = 0.01

low = 0.0
high = k
ans = (high + low) / 2.0
b = 0
while abs(ans ** 2 - k) >= epsilon:
    b = b + 1
    if ans ** 2 < k:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0

guess = k / 2.0
n = 0
while abs(guess * guess - k) >= epsilon:
    n = n + 1
    guess = guess - (((guess ** 2) - k) / (2 * guess))

print('bisection halvings', b)
print('newton steps', n)
print('bisection ans', ans)
print('newton guess', guess)

Sample Run:

bisection halvings 37
newton steps 15
bisection ans 3513.6417004036266
newton guess 3513.641700572214
FindThe two counts, the two answers, and what the gap means.
Given
  • The target is 12345678.0 and epsilon is 0.01 for both searches.

  • The bisection brackets with low 0.0 and high the target.

  • The Newton search starts at half the target.

Solution

Keep the two searches independent

$$\texttt{b}\ \text{and}\ \texttt{n}$$

Two counters, one per search, and each search sets up its own names. Sharing a counter would make the second count include the first.

$$\varepsilon=0.01\ \text{for both}$$

The comparison is only fair if both are asked for the same quality of answer.

Read the counts

$$\texttt{bisection halvings 37}$$

The interval starts 12.3 million wide, and 37 halvings only bring it down to 0.00009, which is not narrow enough to guarantee a hit: near 3513.64 the square moves about seven thousand times as fast as the root, so the guess has to land within about one and a half millionths of it, and that takes forty-two or forty-three halvings. This run stopped at 37 because the midpoint of that wider interval happened to land 0.00000017 from the root, inside the window.

$$\texttt{newton steps 15}$$

Fewer than half as many, and the early steps are doing most of the work: the first few simply halve the guess, and the last few add digits in bulk.

Compare the two answers

$$\texttt{3513.6417004036266}\ \text{and}\ \texttt{3513.641700572214}$$

They agree to nine digits and then part company, both well inside the epsilon that was asked for. Neither is the true root, and neither claims to be.

Answer $$\boxed{37\ \text{halvings},\ 15\ \text{Newton steps}}$$
Check

Check the answers rather than the counts: both square back to 12345678 to within about a hundredth, which is the epsilon that was asked for. And 3500 squared is 12.25 million, so the root of 12.35 million has to sit a little above 3500, which both answers do.

37 against 15 on this target, where a stepping search with the same fine step would have needed about 35 million. On a target of 25 it was 13 against 4.

Measuring beats guessing. Both of these counts were printed by the program rather than argued for, and that is the habit this chapter is really teaching: if a claim about cost matters, make the program count.

Checkpoint
§03.6 — two Newton steps by hand

Two rounds of the Newton step for the square root of 9, starting from a deliberately poor guess.

k = 9.0
guess = 4.0
for i in range(2):
    guess = guess - ((guess ** 2 - k) / (2 * guess))
    print(i, guess)
print(format(guess, '.6f'))
Find
  1. (a) Write exactly what this prints.

  2. (b) Say how many digits of the answer are right after each of the two steps.

Given
  • The target is 9.0 and the starting guess is 4.0.

  • The step is the guess minus the height divided by twice the guess.

  • The loop runs exactly twice, and the last line prints to six places.

IPython console
Hint 1/4

You are asked for printed characters from two arithmetic steps, so do them one at a time and keep the whole value rather than rounding as you go.

Hint 2/4

The step is guess - (guess ** 2 - k) / (2 * guess). The numerator is how far the square is from the target and the denominator is twice the guess.

Hint 3/4

First step, with guess 4 and target 9: the numerator is 16 minus 9, which is 7, and the denominator is 8, so the new guess is 4 minus 0.875, that is 3.125.

Hint 4/4

It prints 0 3.125, then 1 3.0025, then 3.002500.

Show solution

Do the first step in whole fractions

$$\texttt{guess}^2-k = 16-9 = 7$$

The height of the curve above zero at the current guess. Keeping it as 7 rather than a decimal keeps the next division exact.

$$7/(2\cdot 4) = 0.875,\quad 4-0.875 = 3.125$$

Subtracting moves towards the root. Adding instead would move away, which is the sign error to watch for.

Do the second step

$$\texttt{3.125}^2-9 = 9.765625-9 = 0.765625$$

The height has fallen from 7 to about 0.77, which is why the next correction is small.

$$0.765625/6.25 = 0.1225,\quad 3.125-0.1225 = 3.0025$$

Exact in decimal here, which is why the printed value is a clean 3.0025 rather than a long float.

Count the correct digits

$$3.125\ \text{against}\ 3$$

One digit right, an error of 0.125.

$$3.0025\ \text{against}\ 3$$

Three digits right, an error of 0.0025, so the count of correct digits went from one to three in a single step.

Answer $$\boxed{\texttt{0 3.125},\ \texttt{1 3.0025},\ \texttt{3.002500}}$$
Check

Check by squaring back: 3.0025 squared is 9.01500625, which is 0.015 above 9, and a guess whose square is that close cannot be more than about 0.0025 away from the root, so the reported error is the right size.

⚠ Starting the Newton search at zero

Every other search on this page starts at zero, so the habit is already formed by the time this one arrives.

wrong$$\texttt{guess = 0.0}$$
right$$\texttt{guess = k / 2.0}\ \text{or any nonzero start}$$
⚠ Dividing by the guess instead of twice the guess

The slope of the guess squared is twice the guess, which is the one fact here that has to be remembered rather than read off the code, and halving it still converges slowly enough to look plausible.

wrong$$\texttt{guess - (guess ** 2 - k) / guess}$$
right$$\texttt{guess - (guess ** 2 - k) / (2 * guess)}$$
⚠ Reusing the square-root step for a cube root

The shape of the line is memorable and the slope is not, so the 2 gets carried over to a problem where the slope is three times the guess squared.

wrong$$\texttt{guess - (guess ** 3 - k) / (2 * guess)}$$
right$$\texttt{guess - (guess ** 3 - k) / (3 * guess ** 2)}$$
Writing any of the searches on this page, in five moves

Any question of the form find the value that satisfies this when no operator hands it over. It is also the checklist to run over a search of yours that prints a wrong answer.

  1. Name the candidate and the target

    Write one name for the guess and one for the thing it is compared against. If the target is negative and the candidates are not, compare against abs of it and put the sign back at the end.

  2. Decide what close enough means

    Whole numbers can use ==. Anything with a decimal point needs a name epsilon and the test abs(difference) < epsilon. Write the name even if you only use it once.

  3. Write the loop so that it must end

    Every pass has to move the candidate, or halve the interval, or apply the step. Add the second exit for a search that can run past its target, so that a failed search stops instead of hanging.

  4. Ask afterwards whether it worked

    On the line after the loop, test the candidate again. The loop ending is not the same as the answer being found, and this one line is what separates the two.

  5. Report the characters the question asked for

    Use format for a fixed number of places. Match the wording of the sample run exactly, spaces included.

Where it goes wrong
  • Reporting the candidate without step 4, which turns no answer into a wrong answer.

  • Leaving out the second exit in step 3, so a search that cannot succeed never stops either.

  • Using == on floats at step 2, which is the same failure in a different disguise.

Bisection search in five lines

When the answer can be bracketed and a guess that is too big proves the answer is smaller. That covers every root and every smallest value for which this becomes true question.

  1. Bracket the answer

    low = 0.0 and high = x, with high = 1.0 instead when x is below 1. Write the guard before anything else; it is the only precondition the method has.

  2. Guess the midpoint

    ans = (high + low) / 2.0, using true division.

  3. Loop on the epsilon test

    while abs(ans ** 2 - x) >= epsilon:. No second exit is needed here, because the interval shrinks towards the answer on every pass rather than marching past it.

  4. Move one end to the guess

    If the square is too small, low = ans, otherwise high = ans. Only one end moves per pass, and it always moves to the midpoint.

  5. Recompute the midpoint at the end of the body

    ans = (high + low) / 2.0 again, after the end has moved. Putting this at the top of the body instead tests the same guess twice.

Where it goes wrong
  • A bracket that does not contain the answer, which looks like slow convergence and never ends.

  • Both ends moving, or the wrong end moving, which throws away the half that held the answer.

  • Testing the interval width instead of the epsilon on the value, which answers a different question from the one asked.

Choosing between the four methods

Before writing any search, and in an exam when a question asks which method is appropriate and why.

  1. Are the candidates whole and few

    Then exhaustive enumeration, in either loop shape. It is the easiest to write and to read, and for a cube root it costs about the cube root of the number in passes.

  2. Is the answer not a whole number

    Then an epsilon is needed. Stepping is acceptable only when the step can be small and the range short, because the cost grows in direct proportion to the accuracy.

  3. Can the answer be bracketed

    Then bisection, which turns fifty thousand passes into thirteen and barely notices when the target grows.

  4. Is the height and its slope easy to write

    Then the Newton step, which is the fastest here. It needs a nonzero start and offers no bracket, so it is the only one of the four that can fail on an arithmetic error.

Where it goes wrong
  • Reaching for bisection when nothing brackets the answer, for instance in a search over whole numbers with no order to exploit.

  • Reaching for the Newton step when the slope is not available, which makes the step impossible to write.

  • Choosing on elegance rather than on the two questions that actually decide it, whether the answer is bracketed and whether the slope is known.

Square root of 2 by stepping, 14139 passes

The same target and the same epsilon as the next example, reached by walking candidates a ten thousandth apart.

x = 2
epsilon = 0.001
step = 0.0001
ans = 0.0
passes = 0
while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
    passes = passes + 1
print('passes', passes)
print('ans', ans)

Sample Run:

passes 14139
ans 1.4138999999998607
FindThe number of passes and the answer.
Given
  • The target is 2 and epsilon is 0.001.

  • The step is 0.0001 and the search starts at 0.0.

Solution

Count what the step forces

$$1.4142/0.0001 \approx 14142$$

The cost is decided before the program runs: it is the distance to the answer divided by the step, and the program counted 14139 of them.

$$\texttt{ans}=1.4138999999998607$$

Short of the true root by about 0.0003, and carrying a tail from fourteen thousand additions of 0.0001.

Answer $$\boxed{\texttt{passes 14139},\ \texttt{1.4138999999998607}}$$
Check

The answer squared is 1.99911, which is 0.00089 from 2 and just inside the epsilon of 0.001, so the loop stopped at the right place.

The cost here is set by the step, which means by the accuracy wanted, and that is the property bisection does not share.

Square root of 2 by bisection, 7 passes

The same target, the same epsilon, halving the interval instead.

x = 2
epsilon = 0.001
low = 0.0
high = x
ans = (high + low) / 2.0
passes = 0
while abs(ans ** 2 - x) >= epsilon:
    passes = passes + 1
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
print('passes', passes)
print('ans', ans)

Sample Run:

passes 7
ans 1.4140625
FindThe number of passes and the answer.
Given
  • The target is 2 and epsilon is 0.001.

  • The bracket is 0.0 to 2, which contains the answer.

Solution

Count what the halving forces

$$2 \rightarrow 1 \rightarrow 0.5 \rightarrow \dots$$

Seven halvings take the width from 2 to about 0.016, which is fine enough for the squares to agree to 0.001 near 1.414.

$$\texttt{ans}=1.4140625$$

Wrong by about 0.00015, which is better than the stepping answer, in two thousand times fewer passes.

Answer $$\boxed{\texttt{passes 7},\ \texttt{1.4140625}}$$
Check

The answer squared is 1.99957, which is 0.00043 from 2, inside the epsilon and closer than the stepping answer. The value is also a clean binary fraction, seven halvings of 2, which is why it prints without a tail.

Halving pays for accuracy in passes per digit rather than in passes per unit, which is why the gap between the two methods widens as epsilon shrinks.

Both find the square root of 2 to the same epsilon and both are correct, but one uses 14139 passes and the other 7, and the difference is entirely in what each pass learns: a step rules out one candidate, a halving rules out half of them.

How to tell them apart

Ask whether a guess that is too big tells you where the answer is. If it does, halve the interval. If it does not, as in a search for a divisor, halving has nothing to discard and stepping is the only option.

Four quarters, and the equality test that works

A loop that adds a quarter four times and then compares the total with 1.0 using an equals sign.

total = 0.0
for i in range(4):
    total = total + 0.25
print(total)
print(total == 1.0)

Sample Run:

1.0
True
FindThe printed total and the answer to the comparison.
Given
  • The step is 0.25 and the loop runs four times.

  • The comparison is total == 1.0.

Solution

Check whether the step is exact

$$0.25 = 2^{-2}$$

A binary fraction, so it is stored exactly and adding it changes nothing about that.

$$4 \times 0.25 = 1.0\ \text{exactly}$$

So the equality test passes, and this loop would be safe to write with == for ever.

Answer $$\boxed{\texttt{1.0},\ \texttt{True}}$$
Check

Print the total at twenty places and every digit after the point is zero, so the value really is one and the True is not a coincidence of rounding.

An exact step makes == work, which is exactly why the habit survives: your first test case is usually a half or a quarter.

Ten tenths, and the same test failing

The same loop shape with a step of a tenth, ten times, tested both ways.

total = 0.0
for i in range(10):
    total = total + 0.1
print(total)
print(total == 1.0)
print(abs(total - 1.0) < 0.0001)

Sample Run:

0.9999999999999999
False
True
FindThe printed total and the two answers.
Given
  • The step is 0.1 and the loop runs ten times.

  • Both total == 1.0 and abs(total - 1.0) < 0.0001 are printed.

Solution

Check whether the step is exact

$$0.1\ \text{is not a binary fraction}$$

The stored value is slightly above a tenth, and ten additions of it land slightly below one.

$$\texttt{total}=0.9999999999999999$$

About 1.1e-16 short, which is enough for == to answer False and far too little for the window to care.

Answer $$\boxed{\texttt{0.9999999999999999},\ \texttt{False},\ \texttt{True}}$$
Check

Change nothing but the step, back to 0.25 and four passes, and the equality test passes again, so the loop and the operator are both innocent.

The window answers the question that was meant in both cases, so writing it always costs nothing and removes a whole class of loops that never end.

Two loops of the same shape, one with an exact step and one without, and the equality test answers True in the first and False in the second while the window answers True in both.

How to tell them apart

You cannot tell by looking whether a decimal step is exact, so do not try. Use the window whenever a value has a decimal point, and keep == for whole numbers, where it is always right.

Scaffolding comes off
The common skeleton
  1. Name the candidate and the target above the loop, and if the target can be negative compare against its size instead.

  2. Decide what close enough means: == for whole numbers, a name epsilon and abs(difference) < epsilon for anything else.

  3. Write the loop so that every pass makes progress, whether that is a step, an increment, or a halving of the interval.

  4. Add a second exit for the case where the search can run past the answer, so that failing stops the loop instead of hanging it.

  5. After the loop, test the candidate again and report the two cases separately, using format for a fixed number of places.

1 · fully worked

Square root of a number the user types, by halving, to a chosen epsilon

The whole skeleton, with every reason spelled out. Read a number above 1 and an epsilon, and report the square root to four places along with the number of halvings used.

x = float(input('Enter a number > 1: '))
epsilon = float(input('Enter epsilon: '))
low = 0.0
high = x
ans = (high + low) / 2.0
guesses = 0
while abs(ans ** 2 - x) >= epsilon:
    guesses = guesses + 1
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
print('Halvings used:', guesses)
print('Square root of ' + format(x, '.2f') + ' is about ' + format(ans, '.4f'))

Sample Run:

Enter a number > 1: 37
Enter epsilon: 0.001
Halvings used: 14
Square root of 37.00 is about 6.0827
FindThe two printed lines, and a reason for each line of the program.
Given
  • The two values typed are 37 and 0.001.

  • The bracket is 0.0 to the number, which is safe because the number is above 1.

  • The report asks for four places and the count of halvings.

Solution

Read both values as floats

$$\texttt{x = float(input(...))}$$

A float, not an int, because an epsilon of 0.001 has to survive the reading and because the number itself may have a decimal point.

$$\varepsilon\ \text{read as well}$$

Making epsilon an input rather than a constant is what lets the same program answer the how many halvings for this accuracy question.

Bracket, then guess the middle

$$\texttt{low = 0.0},\ \texttt{high = x}$$

Safe for a number above 1, since the root of such a number is between 0 and the number itself.

$$\texttt{ans = (high + low) / 2.0}$$

Computed once before the loop, because the loop condition tests a guess and there has to be one to test.

Halve until the squares agree

$$\texttt{while abs(ans ** 2 - x) >= epsilon}$$

The window, written as the opposite of acceptance so that the loop runs while the guess is not yet good enough.

$$\texttt{if ans ** 2 < x: low = ans else: high = ans}$$

The half that cannot hold the answer is discarded, and only one end moves per pass.

$$\texttt{ans = (high + low) / 2.0}$$

At the end of the body, after the end has moved, so the next test sees a new guess.

Report the characters the question asked for

$$\texttt{Halvings used: 14}$$

The counter was increased in the same block as the halving, so the count and the work agree.

$$\texttt{format(ans, '.4f')}$$

Four places, because the question said so. The stored value is longer and the comparison inside the loop used all of it.

Answer $$\boxed{\texttt{Halvings used: 14},\ \texttt{Square root of 37.00 is about 6.0827}}$$
Check

6.0827 squared is 37.0000 to four places, and 6 squared is 36 while 6.1 squared is 37.21, so the root of 37 has to lie between 6 and 6.1, which it does. The count is also the right order: 37 halved 14 times is about 0.0023, which is the accuracy the epsilon demanded.

14 halvings for an epsilon of 0.001, against 13 for 0.01.

Every line of this program is one of the five skeleton steps. The next rung is the same skeleton with whole numbers, which removes the epsilon and nothing else.

2 · you write the reasoning

Same skeleton, easier problem: whole numbers, so no epsilon and no halving. The program below reads an integer and reports its whole-number cube root or says there is none. The steps are listed with the reasoning removed. Write your own reason for each before opening it. You are not being asked to write any code here, only to say why each line is where it is.

x = int(input('Enter an integer: '))
ans = 0
while ans ** 3 < abs(x):
    ans = ans + 1
if ans ** 3 != abs(x):
    print(x, 'is not a perfect cube')
else:
    if x < 0:
        ans = -ans
    print('Cube root of', x, 'is', ans)

Sample Run:

Enter an integer: -1728
Cube root of -1728 is -12
  1. The value read is converted with int rather than float.

    reasoning

    Because the answer has to be a whole number for the equality test to mean anything. Reading the value as a float would make ans ** 3 != x a comparison between a whole number and a float, and the whole point of this problem is that it can be decided exactly.

  2. The loop condition compares against abs(x) and not x.

    reasoning

    Because the candidates count upwards from 0 and are never negative. Comparing against a negative x would make the condition false at once, the loop would never run, and the program would report that 0 is the answer.

  3. The candidate starts at 0 and is increased by 1 in the body.

    reasoning

    Because the set of candidates is the whole numbers in order, and moving by one is what makes the loop end. Leaving the increase out, or putting it outside the block, gives a loop that tests the same candidate for ever.

  4. There is a test after the loop, using != on whole numbers.

    reasoning

    Because the loop leaving only proves the cube has reached the size of x, not that it equals it. != is safe here in a way it would not be for floats, since both sides are whole numbers.

  5. The sign is put back after the answer is known, not before.

    reasoning

    Because the search runs on the size and the sign is a separate fact about the answer. Negating earlier would break the comparison the loop depends on, and negating never would report a positive root for a negative cube.

3 · find the buried error

Harder, and this one is somebody else's work. It is meant to read a positive integer and print the largest whole number whose square is at most that integer, together with how many passes its loop made. For 50 the right answers are 7 and 8. The first of those two lines comes out wrong, and exactly two of the steps below are wrong.

x = int(input('Enter a positive integer: '))
ans = 0
tested = 0
while ans * ans <= x:
    ans = ans + 1.0
    tested = tested + 1
print('Integer square root of', x, 'is', ans)
print('Passes:', tested)

Sample Run:

Enter a positive integer: 50
Integer square root of 50 is 8.0
Passes: 8
  1. Read the number as a whole number.

  2. Start the candidate at 0 and the counter at 0, above the loop.

  3. Move to the next candidate with ans = ans + 1.0.

  4. Keep going while the square of the candidate is at most x.

  5. Report the candidate the loop stopped on.

  6. Report the counter as the number of passes the loop made.

the two buried errors (2)
⚠ step 3

Adding 1.0 rather than 1 turns the candidate into a float on the first pass, so the answer prints as 8.0 where a whole number was asked for. The search itself still works; only the type and therefore the printed characters are wrong.

A float literal looks like a harmless way to write one, and the program runs without complaint. On paper the marker is comparing characters, and 8.0 is not 8.

right

ans = ans + 1

⚠ step 5

The loop leaves on the first candidate whose square is above x, so the candidate it is holding is one too many. For 50 it holds 8, and the largest whole number whose square is at most 50 is 7.

The loop has just stopped, so the value in hand feels like the answer, and for a perfect square such as 49 the off-by-one is invisible because the loop stops one past a candidate that was exactly right.

right

print('... is', ans - 1)

4 · the bare problem
§03.4 — an by halving, no scaffolding

Nothing is scaffolded this time. Write a program, Sec03_Q6.py, that reads a positive whole number and prints the largest whole number whose square is at most that number, its square, and the number of halvings used. Use bisection on whole numbers only: no epsilon, no floats anywhere, and no functions of your own.

Sample Run, first line only; the three lines that follow it are part (b):

Enter a positive integer: 200
Find
  1. (a) Write the program.

  2. (b) Write the exact three lines it prints for that run.

Given
  • For 200 the answer is 14, because 14 squared is 196 and 15 squared is 225.

  • Whole division // is available and true division would introduce a float.

  • The loop should stop when the two ends are next to each other.

Hint 1/4

With no epsilon the question becomes: when is the interval small enough? Decide that first, because it is the loop condition.

Hint 2/4

On whole numbers the interval is small enough when the two ends differ by one, so the condition is while high - low > 1. The midpoint has to be a whole number too, which means (low + high) // 2.

Hint 3/4

Starting from 0 and 200 the midpoints are 100, 50, 25, 12, 18, 15, 13 and 14, and the low end ends up at 14 while the high end is 15.

Hint 4/4

It prints the answer 14, its square 196, and 8 halvings.

Show solution

Turn the epsilon into a gap

$$\texttt{while high - low > 1}$$

On whole numbers there is nothing between two neighbours, so a gap of one means the search is finished. This replaces the epsilon test and cannot fail to end.

$$\texttt{mid = (low + high) // 2}$$

Whole division, so the midpoint is a candidate rather than a value between candidates. True division here would drag the whole program into floats.

Keep the invariant explicit

$$\texttt{mid * mid <= x} \Rightarrow \texttt{low = mid}$$

The low end always satisfies the condition and the high end never does, which is what makes the answer readable off low at the end.

$$\text{otherwise}\ \texttt{high = mid}$$

One end per pass, as always, and the gap halves each time.

Read the three reported numbers

$$\texttt{low}=14,\ \texttt{low * low}=196$$

196 is at most 200 and 225 is not, so 14 really is the largest such whole number.

$$\texttt{Halvings used: 8}$$

Eight passes to bring a gap of 200 down to 1, which is what doubling 8 times from 1 gives, 256.

Answer $$\boxed{\texttt{14},\ \texttt{196},\ \texttt{8}}$$
Check

Check against the exhaustive search on the same number: counting upwards from 0, the first candidate whose square passes 200 is 15, so the answer is 14 and the two methods agree. The exhaustive version would use 15 passes against these 8, and the gap grows quickly with the number.

Full exam-style question

One program, a halving loop and a counted loop, nine printed linesexam format

This is the shape the tracing question on this course's paper takes: a short program with no input, mixing a conditional loop, an if chain and a counted loop, and the marks are for the characters. Write the exact output before reading on.

x = 20
low = 0
high = x
steps = 0
while high - low > 1:
    mid = (low + high) // 2
    if mid * mid <= x:
        low = mid
    else:
        high = mid
    steps = steps + 1
    print('step', steps, 'low', low, 'high', high)
print('floor of the root is', low)
for k in range(low, low + 3):
    print(k, k * k, k * k <= x)
FindThe nine printed lines, exactly.
Given
  • The target is 20, and the interval starts at low 0 and high 20.

  • The midpoint uses //, so every value in the program stays a whole number.

  • The loop ends when the two ends differ by one, and the counted loop afterwards runs over three values.

Solution

Work out the halving passes first

$$\texttt{mid}=10,\ 100 > 20 \Rightarrow \texttt{high}=10$$

The first pass, and the printed line shows the interval after the move rather than before it.

$$\texttt{mid}=5,\ 25 > 20 \Rightarrow \texttt{high}=5$$

The second. Note that the whole-number midpoint of 0 and 5 is 2, not 2.5, which is what makes the next line surprising.

Follow the whole-number midpoints

$$(0+5)//2 = 2,\ 4 \le 20 \Rightarrow \texttt{low}=2$$

Whole division cuts 2.5 down to 2, so the interval becomes 2 to 5 and the search is now closing from below.

$$(2+5)//2=3,\ (3+5)//2=4$$

Two more passes, each moving the low end up, giving the intervals 3 to 5 and 4 to 5.

Stop on the gap and report

$$\texttt{high - low}=1$$

The condition fails after five passes, so five trace lines are printed and the floor of the root is the low end, 4.

$$\texttt{floor of the root is 4}$$

16 is at most 20 and 25 is not, so 4 is right.

Run the counted loop after it

$$\texttt{range(4, 7)}\rightarrow 4,5,6$$

Three values, because the stop is never produced, so three more printed lines.

$$\texttt{4 16 True},\ \texttt{5 25 False},\ \texttt{6 36 False}$$

Each line prints the candidate, its square and the comparison, which makes the answer of the first part checkable inside the same output.

Answer $$\boxed{5\ \text{step lines},\ \texttt{floor of the root is 4},\ 3\ \text{table lines}}$$
Check

The last three lines verify the first six: 4 squared is at most 20 and 5 squared is not, which is exactly the property the halving loop was searching for, so a trace that ends anywhere but 4 contradicts the program's own output.

Five halvings for a target of 20, against the 5 passes an exhaustive count from 0 would also have used here. The halving only pays off once the target is large.

On paper, write the three columns low, high and mid, and put the printed characters in a fourth. The join between the two loops is where most marks are lost, because the counted loop starts from a value the first loop computed.

The whole run, to check yourself against:

step 1 low 0 high 10
step 2 low 0 high 5
step 3 low 2 high 5
step 4 low 3 high 5
step 5 low 4 high 5
floor of the root is 4
4 16 True
5 25 False
6 36 False

Practice

A · concept 4 questions
1§03.1 — whether a search that stops has found something

A guess-and-check loop for a whole-number cube root has just stopped running. Decide whether the claim below is true and give the reason in one sentence.

The claim: the loop ended, so the candidate it is holding is the cube root of the number.

Find(a) True or false, with the reason.
Given
  • The loop is while ans ** 3 < x: ans = ans + 1.

  • The number x is a positive whole number.

  • No test has been run since the loop ended.

Hint 1/4

Ask what the condition being false actually tells you, rather than what you hoped it would tell you.

Hint 2/4

The loop leaves when the cube of the candidate is not below x. That covers two cases, equal and above, and only one of them is a found answer.

Hint 3/4

With x equal to 10 the candidates are 0, 1, 2 and 3, and the loop leaves holding 3, whose cube is 27. Nothing about 27 is 10.

Hint 4/4

The claim is false; the candidate is the smallest one whose cube reaches x, so an equality test after the loop is what decides.

Show solution

Turn the ending into a statement

$$\neg(\texttt{ans}^3 < x) \Rightarrow \texttt{ans}^3 \ge x$$

That is all the loop guarantees. Written this way the gap is obvious: greater or equal, not equal.

$$x=10 \Rightarrow \texttt{ans}=3,\ 27 \ge 10$$

One counter-case is enough to settle a universal claim.

Say what does settle it

$$\texttt{if ans ** 3 != x}$$

One line, after the loop, and the two reports hang off it. Leaving it out is the single most common mistake in this chapter.

Answer $$\boxed{\text{False: the loop only proves } \texttt{ans}^3 \ge x}$$
Check

Check the other direction as well: on a target of 8 the same loop does stop with the right answer, which is why the shortcut survives casual testing. A claim that holds on 8 and fails on 10 is false.

2§03.3 — which change makes a stepping search report failure

A stepping search for a square root has an epsilon and a step, and it currently succeeds. One of the four changes below makes it report failure on a target that still has a perfectly good answer.

while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
Find(a) Which single change makes the search report failure?
Given
  • The target x is 2, epsilon is 0.01 and step is 0.001, and this combination succeeds.

  • Reporting failure means leaving the loop with the distance still at or above epsilon.

Hint 1/4

Work out what has to be true for the search to succeed at all, and then ask which change could break that.

Hint 2/4

Success means one candidate lands inside the window that epsilon opens. The window has a width, and the candidates have a spacing.

Hint 3/4

With a target of 2 and epsilon 0.01 the window on the candidate runs from about 1.4107 to 1.4177, which is 0.0071 wide. A step of 0.1 produces candidates at 1.4 and 1.5, and neither is inside.

Hint 4/4

The failing change is the step going up to 0.1.

Show solution

Measure the window on the candidate

$$\sqrt{1.99}\approx 1.4107,\ \sqrt{2.01}\approx 1.4177$$

The acceptance test is on the square, so to compare it with a step it has to be turned into a range of candidates.

$$\text{width}\approx 0.0071$$

That is the number the step has to be smaller than. Nothing in the code compares the two, which is why this bug is quiet.

Test each change against that

$$\texttt{step}=0.1 > 0.0071$$

Candidates at 1.4 and 1.5 straddle the window, so the search steps over it and the second exit ends the loop with the distance at 0.25.

$$\varepsilon=0.1 \Rightarrow \text{window} \approx 0.071$$

Ten times wider, so a step of 0.001 lands in it many times over. A wider window is never harder to hit.

Answer $$\boxed{\text{the step, raised to } 0.1}$$
Check

Run the failing case and then shrink only the step back: with 0.001 the same program succeeds, so the step is the cause and not the target.

3§03.5 — whether the printed value is the stored value

A program prints a float and the screen shows a short, tidy decimal. Decide whether the claim below is true and give the reason in one sentence.

The claim: print(0.1) shows 0.1, so the value in memory is exactly one tenth.

Find(a) True or false, with the reason.
Given
  • The value was written in the source as 0.1.

  • Nothing has been added to it or multiplied by it.

Hint 1/4

Separate two questions: what is stored, and what is shown. The claim uses the second to settle the first.

Hint 2/4

Printing chooses the shortest text that would read back as the same stored value, which for a value very near a tenth is the text 0.1.

Hint 3/4

Ask for twenty places instead and the same value shows as 0.10000000000000000555, so the stored value is above a tenth.

Hint 4/4

The claim is false, and format(0.1, '.20f') is how you see it.

Show solution

Ask for more places

$$\texttt{format(0.1, '.20f')}=\texttt{0.10000000000000000555}$$

Twenty places is past the point where a double has any information left, so what appears after the sixteenth digit is the stored value being written out in full.

$$\texttt{format(0.5, '.20f')}=\texttt{0.50000000000000000000}$$

A half is a binary fraction, so this one really is exact. The contrast is what shows the cause is the decimal and not the printing.

Connect it to the comparison

$$\texttt{0.1 + 0.2}\ne\texttt{0.3}$$

Two values each slightly high add to something clearly high, and now the shortest text is long, which is the visible symptom.

Answer $$\boxed{\text{False: stored } 0.1 \text{ is } 0.10000000000000000555}$$
Check

Check with a value that has to be exact: a half prints as 0.5 at twenty places with zeros all the way, so the long tail on a tenth is not an artefact of the formatting.

4§03.5 — which call turns 8.999999999999998 into 9

A search has produced 8.999999999999998 and the answer has to be reported as a whole number. Four calls are on the table.

value = 8.999999999999998
Find(a) Which call reports the whole number 9 as a number?
Given
  • The true answer is 9, and the value is short of it by about 2e-15.

  • int cuts towards zero, round goes to the nearest whole number.

  • format hands back characters rather than a number.

Hint 1/4

Two things are being asked at once: the right arithmetic, and the right type. Check both for each call.

Hint 2/4

int cuts towards zero, round goes to the nearest whole number, and format produces characters.

Hint 3/4

On 8.999999999999998 cutting gives 8 and rounding gives 9. Formatting to no places gives the two characters 9, which is text.

Hint 4/4

round(value) is the one that gives the number 9.

Show solution

Rule out the cutting call

$$\texttt{int(8.999999999999998)}=8$$

It removes everything after the point, and the value has not reached 9 yet. This is precisely the failure the page opens with.

$$\texttt{round(8.999999999999998)}=9$$

Nearest whole number, and 9 is nearer than 8 by a very wide margin.

Rule out the two that look right

$$\texttt{format(value, '.0f')}\rightarrow\texttt{'9'}$$

Right characters, wrong kind of value: a comparison against 9 would then be comparing text with a number.

$$\texttt{int(8.4 + 0.5)}=8$$

The add-a-half recipe rounds 8.4 up to 8.9 and then cuts to 8, so it agrees with round here and disagrees elsewhere, which makes it the worst of the four to learn.

Answer $$\boxed{\texttt{round(value)}}$$
Check

Test each candidate on a value that must round down, such as 8.4: round gives 8, int gives 8, and the add-a-half recipe gives 8, but on 8.6 the recipe gives 9 and int gives 8, so only round tracks the intention on both sides.

B · computation 6 questions
1§03.2 — a divisor search that prints every candidate it tries

A counted search for the smallest divisor above 1, printing each candidate as it goes.

n = 45
for d in range(2, 10):
    print('trying', d)
    if n % d == 0:
        break
print('d =', d, 'divides?', n % d == 0)
Find
  1. (a) Write exactly what this prints.

  2. (b) Say what the last line would print if the number were a prime such as 47.

Given
  • The number is 45.

  • Candidates run over range(2, 10).

  • The last line reads the loop variable after the loop has finished.

IPython console
Hint 1/4

Two things are printed from two different places: one line per candidate from inside the loop, and one line from after it.

Hint 2/4

The break fires on the first candidate that divides the number, and the loop variable keeps that candidate. With no divisor at all the range simply runs out and the variable holds its last value.

Hint 3/4

For 45: 45 divided by 2 leaves a remainder, so trying 2 prints and the loop carries on; 45 divided by 3 is exact, so trying 3 prints and then the break fires.

Hint 4/4

It prints two trying lines and then d = 3 divides? True.

Show solution

Walk the candidates in order

$$\texttt{45 \% 2}=1$$

Not a divisor, so no break; the printing happens before the test, so the line appears anyway.

$$\texttt{45 \% 3}=0$$

A divisor, so the line prints and then the loop is left immediately; candidates 4 to 9 are never tried.

Read the line after the loop

$$\texttt{d}=3,\ \texttt{45 \% 3 == 0}$$

The test is repeated outside the loop, which is what makes the report trustworthy on both endings.

$$\text{prime case:}\ \texttt{d}=9$$

The range ran out, so the variable holds the last candidate and the repeated test answers False.

Answer $$\boxed{\texttt{trying 2},\ \texttt{trying 3},\ \texttt{d = 3 divides? True}}$$
Check

Check the divisor itself: 3 times 15 is 45, and 45 is odd so 2 was never going to work, which matches the two lines printed and no more.

2§03.1 — peeling the digits off a whole number

A loop that walks the place values of a whole number using whole division and remainder, with no strings anywhere.

n = 4236
place = 1
while n // place > 0:
    print(place, n // place, (n // place) % 10)
    place = place * 10
print('place =', place)
Find(a) Write exactly what this prints.
Given
  • The number is 4236 and the place starts at 1.

  • n // place removes the digits below that place.

  • % 10 keeps only the last digit of what is left.

IPython console
Hint 1/4

The three printed values are all built from the same two operations, so keep three columns: the place, the number with the low digits removed, and that value's last digit.

Hint 2/4

Whole division by a power of ten shifts the number rightwards, and % 10 picks off the rightmost digit of the shifted value. The loop ends when the shift has emptied the number.

Hint 3/4

With 4236: at place 1 the shifted value is 4236 and its last digit is 6; at place 10 it is 423 and 3; at place 100 it is 42 and 2; at place 1000 it is 4 and 4.

Hint 4/4

It prints four lines, one per digit from the right, then place = 10000.

Show solution

Do the first two passes carefully

$$\texttt{4236 // 1}=4236,\ \texttt{4236 \% 10}=6$$

Dividing by one changes nothing, so the first pass exists only to pick off the last digit.

$$\texttt{4236 // 10}=423,\ \texttt{423 \% 10}=3$$

The shift drops the 6 completely rather than rounding it, which is the reason for // instead of /.

Finish and read the exit

$$\texttt{// 100}\rightarrow 42,\ \texttt{// 1000}\rightarrow 4$$

Two more passes, giving the digits 2 and 4.

$$\texttt{4236 // 10000}=0$$

The condition fails, the loop ends, and the place is left at 10000, one step past the number's own size.

Answer $$\boxed{4\ \text{lines, then}\ \texttt{place = 10000}}$$
Check

Count the digits from the answer: the loop printed four lines and 4236 has four digits, and the final place, 10000, has exactly one more zero than the number has digits.

3§03.3 — a stepping search that gives up

A stepping search for the square root of 3, with a step that is too coarse for the window it has to hit.

x = 3
epsilon = 0.05
step = 0.1
ans = 0.0
passes = 0
while abs(ans ** 2 - x) >= epsilon and ans * ans <= x:
    ans = ans + step
    passes = passes + 1
print(passes, format(ans, '.1f'))
print(abs(ans ** 2 - x) < epsilon)
Find
  1. (a) Write exactly what this prints.

  2. (b) Say which of the two exits ended the loop.

Given
  • The target is 3, epsilon is 0.05 and the step is 0.1.

  • The loop has two exits: close enough, or the square has passed the target.

  • The candidate is printed to one place and then the acceptance test is printed.

IPython console
Hint 1/4

Two exits means two things to watch, so decide first which one can fire earlier for this target.

Hint 2/4

The first exit needs the square to come within 0.05 of 3; the second fires as soon as the square goes above 3. The square root of 3 is about 1.732.

Hint 3/4

Stepping by 0.1 from 0.0 the candidates near the answer are 1.7, whose square is 2.89, and 1.8, whose square is 3.24. The distance at 1.7 is 0.11, still too big, and at 1.8 the square has passed 3.

Hint 4/4

It prints 18 1.8 and then False.

Show solution

Turn epsilon into a range of candidates

$$\sqrt{2.95}\approx 1.7176,\ \sqrt{3.05}\approx 1.7464$$

The test is on the square, so this is the only way to compare it with a step.

$$\text{width}\approx 0.029 < 0.1$$

No candidate on a grid of 0.1 can land inside a window that narrow, so failure was decided before the loop ran.

Find the pass where the second exit fires

$$1.7^2=2.89,\ \lvert 2.89-3\rvert = 0.11$$

Above epsilon, and the square is still below 3, so the loop takes one more step.

$$1.8^2=3.24 > 3$$

The second half of the condition is now false, the loop ends, and the candidate printed to one place is 1.8.

Count the passes

$$1.8/0.1 = 18$$

The candidate climbed from 0.0 to 1.8 in steps of 0.1, so eighteen passes, which is the number printed.

Answer $$\boxed{\texttt{18 1.8},\ \texttt{False}}$$
Check

Check by shrinking only the step: with 0.001 the same program lands inside the window and prints True, so the target was never the problem.

4§03.4 — three halvings on an interval that does not start at zero

A bisection for the square root of 5, bracketed between 1 and 4 rather than between 0 and the target, and run for a fixed three passes.

low = 1.0
high = 4.0
target = 5
for p in range(1, 4):
    mid = (low + high) / 2
    if mid * mid < target:
        low = mid
    else:
        high = mid
    print(p, format(low, '.4f'), format(high, '.4f'))
Find
  1. (a) Write exactly what this prints.

  2. (b) Say whether the answer is still inside the interval after the third pass.

Given
  • The interval starts at low 1.0 and high 4.0, and the target is 5.

  • Each pass prints the pass number and both ends to four places, after the update.

  • The square root of 5 is about 2.2360.

IPython console
Hint 1/4

The printing happens after the update this time, so each line shows the interval the next pass will start from.

Hint 2/4

The midpoint is the average of the ends. If its square is below the target the answer is above it, so the low end moves up; otherwise the high end moves down.

Hint 3/4

From 1 and 4 the midpoint is 2.5, whose square is 6.25, above 5, so high becomes 2.5. Then the midpoint is 1.75, whose square is 3.0625, below 5, so low becomes 1.75.

Hint 4/4

It prints 1 1.0000 2.5000, then 2 1.7500 2.5000, then 3 2.1250 2.5000.

Show solution

Do the first pass

$$\texttt{mid}=(1+4)/2=2.5,\ 6.25 > 5$$

So the answer is below the midpoint and the high end comes down to it. The printed line shows the interval after that move.

$$\text{interval}\ 1.0\ \text{to}\ 2.5$$

Width 1.5, which is half of 3.

Do the other two

$$\texttt{mid}=1.75,\ 3.0625 < 5$$

Below the target, so the low end goes up and the interval becomes 1.75 to 2.5.

$$\texttt{mid}=2.125,\ 4.515625 < 5$$

Below again, so the low end moves once more, to 2.125.

Check the bracket still holds

$$2.125 < 2.2360 < 2.5$$

The answer is inside, which it has to be: a halving only ever discards the side whose square is on the wrong side of the target.

Answer $$\boxed{\texttt{3 2.1250 2.5000},\ \text{answer inside}}$$
Check

Square both ends of the final interval: 2.125 squared is 4.52 and 2.5 squared is 6.25, and the target 5 lies between them, so the bracket is sound at the end as well as at the start.

5§03.1 — a whole-number fourth root, or the two neighbours

Write a program, Sec03_Q1.py, that reads a positive whole number and searches for its whole-number fourth root. If the number is a perfect fourth power, report the root. If it is not, report that, and also report the two whole numbers whose fourth powers the number falls between. Use no functions of your own and nothing from a later week.

Sample Run:

Enter a positive integer: 1296
Fourth root of 1296 is 6

A second run, for a number that is not a perfect fourth power, has to print three lines: the prompt with the typed number, the not a perfect fourth power line, and the two neighbours.

Find
  1. (a) Write the program.

  2. (b) Give the exact three lines for a run where 1300 is typed in.

Given
  • The candidates are the whole numbers from 0 upwards.

  • 1296 is 6 to the fourth power, so the first run reports 6.

  • For a number such as 1300 the neighbours are 6 and 7.

Hint 1/4

This is the guess-and-check shape with one extra report, so start by writing the loop and the test after it, and only then think about the neighbours.

Hint 2/4

The loop while ans 4 < x: ans = ans + 1 leaves holding the smallest candidate whose fourth power reaches x. After it, ans 4 == x decides between the two reports.

Hint 3/4

For 1300 the candidates are 0 to 7, because 6 to the fourth is 1296 and 7 to the fourth is 2401. So the loop leaves holding 7 and the neighbours are 7 minus 1 and 7.

Hint 4/4

The failing run prints the prompt line, then 1300 is not a perfect fourth power, then The two whole neighbours are 6 and 7.

Show solution

Write the search first

$$\texttt{while ans ** 4 < x: ans = ans + 1}$$

The same three parts as every search on this page, with the power changed. Nothing about fourth powers needs a different shape.

$$\texttt{ans}\ \text{stops at the first candidate with}\ \texttt{ans}^4 \ge x$$

Which is the found answer when the powers are equal and the upper neighbour otherwise.

Split the two reports

$$\texttt{if ans ** 4 == x}$$

Equality between whole numbers, so == is safe here; this is not a float comparison.

$$\texttt{print(ans - 1, 'and', ans)}$$

No second loop is needed for the neighbours, because the candidate before the stopping one is the last that was too small.

Check the edge cases before handing it in

$$x=1 \Rightarrow \texttt{ans}=1$$

The loop runs once, 1 to the fourth is 1, and the report is the root, which is right.

$$x=0 \Rightarrow \texttt{ans}=0$$

The loop never runs, 0 to the fourth is 0, and the report is 0. The neighbour line would have printed -1, which is why the guard is on the equality and not on the loop.

Answer $$\boxed{\texttt{Fourth root of 1296 is 6}}$$
Check

Verify the two powers by hand: 6 squared is 36 and 36 squared is 1296, so the first run is right; 7 squared is 49 and 49 squared is 2401, so 1300 really does fall between the two neighbours reported.

6§03.6 — a Newton search that shows its working

Write a program, Sec03_Q2.py, that reads a positive number and finds its square root with the Newton step to within 0.0001, printing every step as it goes and then the answer to six places and the number of steps used. Start from half the number.

Sample Run:

Enter a positive number: 50
step 1 13.500000

The run continues for four more step lines and then prints the answer and the count.

Find
  1. (a) Write the program.

  2. (b) Give the exact remaining lines of the run in which 50 was typed.

Given
  • The step is the guess minus the height divided by twice the guess.

  • epsilon is 0.0001 and the starting guess is the number divided by 2.

  • The square root of 50 is about 7.0710678.

Hint 1/4

The shape is a while loop whose condition measures how far the square of the guess is from the target, so write that condition before anything else.

Hint 2/4

The condition is while abs(guess * guess - k) >= epsilon, and the body is one assignment: guess = guess - ((guess ** 2 - k) / (2 * guess)).

Hint 3/4

Starting from 25.0 with a target of 50: the height is 625 minus 50, and twice the guess is 50, so the correction is 11.5 and the first printed step is 13.500000.

Hint 4/4

Five step lines in total, then Square root of 50.0 is about 7.071068 and Steps used: 5.

Show solution

Set up the start and the window

$$\texttt{guess = k / 2.0}$$

Any positive start works; half the number is the textbook's choice and costs one or two extra steps at most.

$$\varepsilon = 0.0001\ \text{on the square}$$

Measured on the square rather than the root, matching every other search on this page so the counts can be compared.

Write the step once and print inside the loop

$$\texttt{guess = guess - ((guess ** 2 - k) / (2 * guess))}$$

The brackets are not decoration: without the inner pair the division would apply to k alone.

$$\texttt{print('step', steps, format(guess, '.6f'))}$$

Printing after the update means step 1 shows the first improved guess rather than the starting value.

Read the count off the run

$$\texttt{Steps used: 5}$$

Five for a target of 50, against 4 for 24 earlier on this page, so the count grows very slowly with the size of the target.

Answer $$\boxed{\texttt{Square root of 50.0 is about 7.071068},\ \texttt{Steps used: 5}}$$
Check

Square the answer: 7.071068 squared is 50.00000... to six places, and 50 is twice 25, so the root has to be 5 times the root of 2, about 7.0710678. The printed value matches that to all six places.

C · exam level 4 questions
1§03.2 — the line that makes a failed search report an answer

Somebody hands in this program for print the smallest whole number above 1 that divides n, or say that n is prime. It works on 91 and reports nonsense on 97. Exactly one of the numbered lines is responsible.

n = 97
for d in range(2, n):      # line 1
    if n % d == 0:         # line 2
        break              # line 3
print('smallest divisor is', d)   # line 4
Find(a) Which line has to change, and to what?
Given
  • For 91 the program prints smallest divisor is 7, which is right.

  • For 97, a prime, it prints smallest divisor is 96.

  • The range and the test are both written correctly for the job.

Hint 1/4

Start from the wrong output rather than from the code: the program printed 96 for a prime, so ask what the name held at that moment and why.

Hint 2/4

A counted loop leaves in two ways, by breaking out or by running out of candidates, and the loop variable holds the last candidate either way. Only a test outside the loop can tell the two apart.

Hint 3/4

For 97 nothing divides it, so the range runs out and the variable holds 96, the last value range(2, 97) produces. The report then prints that number as though a break had happened.

Hint 4/4

Line 4 is the culprit, and it has to be guarded by a test such as if n % d == 0: or by a flag set inside the loop.

Show solution

Explain the wrong number first

$$\texttt{range(2, 97)}\ \text{ends at}\ 96$$

The stop value is never produced, so the last candidate really is 96, and that is what the variable holds when the range runs out.

$$\text{no break} \Rightarrow \text{no answer}$$

Nothing in the program records whether a break happened, which is the gap.

Rule out the other three lines

$$\texttt{range(2, n)}\ \text{is right}$$

A divisor of n above 1 and below n is what is wanted, and this range is exactly that set; stopping at n minus 1 would drop nothing but would not fix anything either.

$$\texttt{n \% d == 0}\ \text{is right}$$

It is the standard test for divisibility, and the program's correct answer on 91 proves the test and the loop are both working.

Write the fix

$$\texttt{smallest = 0}\ \text{before the loop}$$

A value outside the set of possible answers, so it can stand for nothing found yet.

$$\texttt{if smallest == 0: print(n, 'is prime')}$$

One line, two reports, and the loop variable is never read outside the loop again.

Answer $$\boxed{\text{line 4, guarded by a flag or a repeated test}}$$
Check

Test the fixed version on both inputs: 91 gives 7 and 97 gives the prime report, so the change fixes the failing case without breaking the passing one, which is what separates a fix from a patch.

2§03.4 — repairing a bisection that never converges below one

A bisection search for a square root hangs when the number typed in is smaller than 1. It works for every number above 1. One change fixes it.

low = 0.0
high = x
ans = (high + low) / 2.0
while abs(ans ** 2 - x) >= epsilon:
    if ans ** 2 < x:
        low = ans
    else:
        high = ans
    ans = (high + low) / 2.0
Find(a) Which change fixes it?
Given
  • With x equal to 0.25 the program never leaves the loop.

  • The square root of 0.25 is 0.5.

  • With x equal to 25 the same program answers in 13 passes.

Hint 1/4

The method has one precondition, so check that first rather than reading the loop body again.

Hint 2/4

Bisection needs the answer to lie between low and high before the loop starts. Nothing in the code checks that, and a bracket that misses the answer produces a loop that cannot succeed.

Hint 3/4

For x equal to 0.25 the bracket is 0.0 to 0.25, and the answer is 0.5, which is outside it. Every pass finds the midpoint too small, so low creeps upwards towards 0.25 for ever.

Hint 4/4

The fix is to widen the upper end to 1.0 when x is below 1.

Show solution

Name the precondition

$$\texttt{low} \le \sqrt{x} \le \texttt{high}$$

This has to hold before the first pass. It is the one thing bisection assumes and the one thing the code never checks.

$$x=0.25:\ \sqrt{x}=0.5 > 0.25$$

So the bracket 0.0 to 0.25 misses the answer, and for every x below 1 it misses it the same way.

Show why the other changes cannot help

$$\text{swapped branches}$$

Then a midpoint too small would move the high end down, discarding the half that contains the answer, and the working case would break too.

$$\texttt{!=}\ \text{on floats}$$

The distance passes through the window rather than landing on its edge, so this condition almost never becomes false.

Write the guard

$$\texttt{if x < 1: high = 1.0 else: high = x}$$

Two lines, and they cover both sides of 1 without needing to know anything else about x.

Answer $$\boxed{\texttt{if x < 1: high = 1.0}}$$
Check

Check the guarded version on both sides of 1: for 0.25 it answers 0.5 immediately and for 25 it still answers in 13 halvings, so the guard fixes the broken case and leaves the working one untouched.

3§03.1 — writing a number as one whole power of another

Exam shape: a search inside a search, with no functions allowed. Write a program, Sec03_Q3.py, that reads a whole number above 1 and reports it as a base raised to a whole power, with the power at least 2, or reports that it cannot be written that way. If more than one pair works, report the one with the smallest base.

Sample Run:

Enter an integer > 1: 2048
2048 = 2 ** 11
Find
  1. (a) Write the program.

  2. (b) Say why the outer search can stop as soon as the square of the base is above the number.

Given
  • 2048 is 2 to the eleventh power.

  • A number such as 90 cannot be written as a whole power with a power of at least 2.

  • The base need not be searched past the point where its square exceeds the number.

Hint 1/4

Two unknowns means two loops, so decide which one is the outer and what each of them is allowed to run over before writing any code.

Hint 2/4

The outer loop tries bases from 2 upwards; the inner one tries powers from 2 upwards while the base raised to that power is still at most the number. A hit is recorded in two names kept outside both loops.

Hint 3/4

For 2048 the base 2 works with a power of 11, and it is found first because the outer loop starts at 2. For 90 no pair is ever recorded, so the two names still hold their starting values at the end.

Hint 4/4

The outer loop can stop when the square of the base passes the number, because the smallest allowed power is 2.

Show solution

Choose what each loop runs over

$$\texttt{base}: 2, 3, 4, \dots\ \text{while}\ \texttt{base * base} \le x$$

The outer loop. The bound comes from the smallest allowed power, and writing it as a multiplication keeps everything in whole numbers.

$$\texttt{power}: 2, 3, \dots\ \text{while}\ \texttt{base ** power} \le x$$

The inner loop. It rises until the power overshoots, which happens quickly, so the total number of passes stays small.

Record a hit outside both loops

$$\texttt{answer\_base = 0}$$

Zero is not a legal base here, so it doubles as nothing found yet, the same trick as the divisor search.

$$\texttt{if base ** power == x}$$

Whole numbers on both sides, so the equality test is exact and no epsilon is needed anywhere in this program.

Report once, after both loops

$$\texttt{if answer\_base == 0: not a perfect power}$$

One test, two reports, outside both loops at the leftmost indentation.

$$\text{smallest base first}$$

Because the outer loop rises and every later hit overwrites nothing that was needed: 2048 is also 32 to the power 2.2, which is not whole, so 2 with 11 is the only pair here anyway.

Answer $$\boxed{\texttt{2048 = 2 ** 11}}$$
Check

Verify the pair by multiplying: 2 to the tenth is 1024 and doubling gives 2048, so the power really is 11. Then check a number that should fail, such as 90, and confirm the program reports no pair rather than printing a base of 0.

4§03.6 — the Newton step for a cube root

The Newton step on this page was written for a square root. A question asks for a cube root of k to within an epsilon, using the same shape of loop. One of the four lines below is the correct body.

while abs(guess ** 3 - k) >= epsilon:
    guess = ???
Find(a) Which line is the correct body?
Given
  • For a square root the body is guess - ((guess ** 2 - k) / (2 * guess)).

  • The height whose zero is wanted is now the guess cubed minus k.

  • The slope of the guess cubed is three times the guess squared.

Hint 1/4

The step has two parts: the height above zero, and the slope at the guess. Identify which part of the line carries each before comparing the choices.

Hint 2/4

The step is the height divided by the slope, subtracted from the guess. For the guess cubed minus k the height is exactly what the loop condition measures, and the slope is three times the guess squared.

Hint 3/4

So the top of the fraction must be guess ** 3 - k, matching the loop condition, and the bottom must be 3 * guess ** 2.

Hint 4/4

The correct body is guess - ((guess ** 3 - k) / (3 * guess ** 2)).

Show solution

Split the step into its two parts

$$\text{step} = \frac{\text{height at the guess}}{\text{slope at the guess}}$$

This is the whole method in one line. Everything else is arithmetic.

$$\text{height} = \texttt{guess ** 3 - k}$$

It has to match the loop condition, otherwise the loop would be measuring one thing and the step correcting another.

Get the slope right

$$\text{slope of}\ \texttt{guess ** 2}\ \text{is}\ 2\,\texttt{guess}$$

Quoted, not derived, and it is the factor in the square-root version.

$$\text{slope of}\ \texttt{guess ** 3}\ \text{is}\ 3\,\texttt{guess}^2$$

Both the number and the power change, which rules out the two choices that alter only one of them.

Answer $$\boxed{\texttt{guess - ((guess ** 3 - k) / (3 * guess ** 2))}}$$
Check

Check the answer by cubing it: 4.641589 cubed is 100.0000 to four places, and 4.6 cubed is 97.3 while 4.7 cubed is 103.8, so the root of 100 has to sit between them, which it does.

D · interleaved 4 questions
1§03.5 — digits inside typed text, counted and averaged

Write a program, Sec03_Q4.py, that reads one line of text and reports how many digit characters it contains, their sum, and their average to two places. If there are no digits, the average line must not be printed at all.

Sample Run:

Enter a code: A3B7C1
digits found: 3
their sum: 11
average: 3.67
Find
  1. (a) Write the program.

  2. (b) Say what the program prints when the text typed is abc, and why the average line has to be guarded.

Given
  • A character can be tested with ch.isdigit().

  • int(ch) turns a single digit character into its value.

  • The average has to be printed to two places.

Hint 1/4

Two counters and one loop over the characters. Decide what each counter means before writing the loop, and leave the reporting until after it.

Hint 2/4

Walking the characters directly with for ch in text: is enough; no positions are needed. Inside, one test decides whether the character contributes, and it contributes to both counters.

Hint 3/4

For A3B7C1 the digit characters are 3, 7 and 1, so the count is 3 and the sum is 11, and 11 divided by 3 is 3.6666..., which to two places is 3.67.

Hint 4/4

For abc it prints the count 0 and the sum 0 and nothing else, because dividing by the count would stop the program.

Show solution

Choose the loop and the two counters

$$\texttt{for ch in text:}$$

Characters, not positions, because nothing here needs to know where a digit was.

$$\texttt{total}\ \text{and}\ \texttt{count}$$

Both start at 0 above the loop, and both are increased in the same branch so they can never get out of step.

Convert on the way in

$$\texttt{total = total + int(ch)}$$

Without the conversion this would join characters together and the sum would come out as the text 371.

$$\texttt{if ch.isdigit()}$$

Keeps the conversion safe: int on a letter stops the program.

Guard the division

$$\texttt{if count > 0}$$

The only line that can fail, and the only one that needs a guard. The two counts above it are always printable.

$$\texttt{format(total / count, '.2f')}$$

A true division here, because an average is not a whole number; using // would have printed 3 instead of 3.67.

Answer $$\boxed{\texttt{digits found: 3},\ \texttt{their sum: 11},\ \texttt{average: 3.67}}$$
Check

Check the average by hand: 3 plus 7 plus 1 is 11, and 11 divided by 3 is 3.6667, which rounds to 3.67 at two places. Then run the empty case and confirm two lines rather than a crash.

2§03.1 — a search over pairs, printed row by row

A search over pairs of whole numbers, with a report inside the outer loop as well as inside the inner one.

n = 6
for a in range(1, 4):
    for b in range(1, 4):
        if a * b == n:
            print(a, b)
    print('row', a, 'done')
Find(a) Write exactly what this prints.
Given
  • The target is 6.

  • Both loops run over range(1, 4), so both take the values 1, 2 and 3.

  • The row line sits in the outer loop, not the inner one.

IPython console
Hint 1/4

Two loops means the order of the printed lines is decided by which loop a print belongs to, so mark that before tracing any arithmetic.

Hint 2/4

The inner loop runs all the way through for each single pass of the outer one, and the row line prints once per outer pass, after its inner loop has finished.

Hint 3/4

With a target of 6 the pairs that multiply to it are 2 with 3 and 3 with 2, and 1 has no partner in the range because 6 is not among 1, 2 and 3.

Hint 4/4

It prints row 1 done, then 2 3, then row 2 done, then 3 2, then row 3 done.

Show solution

Settle which print belongs where

$$\texttt{print(a, b)}\ \text{inner}$$

It sits two levels in, under the test, so it can fire zero or more times per outer pass.

$$\texttt{print('row', a, 'done')}\ \text{outer}$$

One level in, so exactly once per outer pass, after the inner loop has run out.

Do the three outer passes

$$a=1:\ 1\cdot 1,1\cdot 2,1\cdot 3$$

None of them is 6, so no pair line, and the row line prints alone.

$$a=2:\ 2\cdot 3=6$$

One hit, printed as 2 3, then the row line.

$$a=3:\ 3\cdot 2=6$$

The same pair the other way round, printed as 3 2, then the last row line.

Answer $$\boxed{5\ \text{lines, beginning}\ \texttt{row 1 done}}$$
Check

Count the tests separately: three outer passes times three inner passes is nine multiplications for two hits, which is the price of an exhaustive search over pairs and the reason a single loop is preferred whenever one of the two values can be computed from the other.

3§03.3 — how many years until a balance reaches a target

Write a program, Sec03_Q5.py, that reads a target amount and reports how many whole years a balance of 10000 needs to reach it, at 15 per cent a year, along with the balance at that point to two places.

Sample Run:

Enter the target amount: 30000
It takes 8 years
Balance is 30590.23TL
Find
  1. (a) Write the program.

  2. (b) Say why this loop cannot be written with a range, and why the reported balance is above the target rather than equal to it.

Given
  • The balance starts at 10000 and grows by 15 per cent each year.

  • The answer is the number of whole years, so the loop count is the answer.

  • The balance has to be reported to two places.

Hint 1/4

The thing being asked for is a count, so decide first whether the count is known before the loop starts. That decides which loop to use.

Hint 2/4

A while loop is needed when the number of passes is the answer. The balance is the name the condition reads, and it has to change inside the block or the loop never ends.

Hint 3/4

Starting from 10000 and multiplying by 1.15: after 7 years the balance is about 26600, still under 30000, so an eighth year happens and takes it to 30590.23.

Hint 4/4

Eight years, and the balance prints as 30590.23TL.

Show solution

Pick the loop shape from what is unknown

$$\text{count is the answer} \Rightarrow \texttt{while}$$

A counted loop needs the count in advance; here it is the result, so the conditional loop is the only option.

$$\texttt{balance}\ \text{and}\ \texttt{years}\ \text{above the loop}$$

Both names the condition and the report read, created before the first test.

Grow and count in the same block

$$\texttt{balance = balance * (1 + rate)}$$

Written with the rate as a name so a different rate is a one-line change rather than a hunt through the condition.

$$\texttt{years = years + 1}$$

In the same block, so a pass of growth and a year always happen together.

Report to two places

$$\texttt{format(balance, '.2f')}$$

The raw value is 30590.228515625, which is not a sum of money; format makes it one without changing the number the condition saw.

$$8\ \text{years}$$

The count of passes, which is what was asked for.

Answer $$\boxed{\texttt{It takes 8 years},\ \texttt{Balance is 30590.23TL}}$$
Check

Check by rough powers: 1.15 to the fifth is about 2.01, so the balance doubles in about five years, reaching 20000; three more years at 15 per cent adds about half again, which lands just past 30000. Eight is therefore the right order and the printed balance agrees.

4§03.5 — four lines of arithmetic with two kinds of number

Eleven values printed on four lines, mixing whole-number and float arithmetic.

print(7 / 2, 7 // 2, 7 % 2)
print(-7 / 2, -7 // 2, -7 % 2)
print(int(-3.7), abs(-3.7))
print(2 ** 10, 2 ** 0.5)
Find
  1. (a) Write exactly what this prints.

  2. (b) Say which two values on the second line are the ones most often written down wrongly.

Given
  • / always gives a float, // and % keep whole numbers whole.

  • With a negative left side, // goes down rather than towards zero.

  • int cuts towards zero and abs removes a sign.

IPython console
Hint 1/4

Take the four lines one at a time and decide for each value whether the answer should carry a decimal point before working out its digits.

Hint 2/4

True division always gives a float even when the division is exact. Whole division rounds downwards, which for a negative value means away from zero, and the remainder then comes out positive.

Hint 3/4

For the second line: -7 divided by 2 is -3.5, so the whole division goes down to -4, and the remainder has to be 1 because -4 times 2 plus 1 is -7.

Hint 4/4

It prints 3.5 3 1, then -3.5 -4 1, then -3 3.7, then 1024 1.4142135623730951.

Show solution

Do the positive line

$$\texttt{7 / 2}=3.5,\ \texttt{7 // 2}=3,\ \texttt{7 \% 2}=1$$

Three operators, three types of answer: a float, a whole number and a whole number.

Do the negative line carefully

$$\texttt{-7 // 2}=-4$$

The whole division goes down, not towards zero, so it passes -3.5 and lands on -4.

$$\texttt{-7 \% 2}=1$$

Forced by the previous line: -4 times 2 is -8, and -8 plus 1 is -7, so the remainder is positive.

Do the last two lines

$$\texttt{int(-3.7)}=-3,\ \texttt{abs(-3.7)}=3.7$$

Cutting goes towards zero here, which is the opposite direction from the whole division above, and that contrast is the whole point of the pair.

$$\texttt{2 ** 0.5}=1.4142135623730951$$

A whole base and a fractional power give a float, printed to as many digits as it takes to read back the same value.

Answer $$\boxed{\texttt{-3.5 -4 1}\ \text{is the line that costs marks}}$$
Check

Check the pair of whole-number operators with the rebuilding rule on both lines: 3 times 2 plus 1 is 7, and -4 times 2 plus 1 is -7, so both remainders are right and both are positive.

Mistake ledger (18 entries)
⚠ Treating the end of the loop as the answer

The loop is the part that looks like work, so it feels finished when it stops; and on a number that does have an exact answer the shortcut gives the right result, so it survives testing.

wrong$$\texttt{while ans ** 3 < x: ans = ans + 1};\;\texttt{print('root is', ans)}$$
right$$\texttt{if ans ** 3 != x: print('no whole root')};\;\texttt{else: print('root is', ans)}$$
⚠ Searching against a negative target

abs is easy to leave out because the search reads correctly without it, and the loop does not crash; it simply runs zero times and reports the candidate 0.

wrong$$\texttt{while ans ** 3 < x}\;\text{with}\;x=-64$$
right$$\texttt{while ans ** 3 < abs(x)}$$
⚠ Moving to the next candidate outside the loop body

The increase looks like part of the reporting rather than part of the search, and one level of indentation is all that separates the two.

wrong$$\texttt{while ans ** 3 < x:}\;\text{body holds only}\;\texttt{print(ans)}$$
right$$\texttt{while ans ** 3 < x:}\;\text{body holds}\;\texttt{print(ans)}\;\text{and}\;\texttt{ans = ans + 1}$$
⚠ Reading the loop variable as though a break had happened

On the first test case there usually is an answer, so the break fires and the value is right.

wrong$$\texttt{for ans in range(0,5): if ans*ans==20: break};\;\texttt{print(ans)}$$
right$$\texttt{found = False};\;\texttt{if ans*ans==20: found = True; break};\;\texttt{if found: print(ans)}$$
⚠ A range that stops one candidate short

range(0, n) reads like the numbers up to n, and for most targets the answer is well inside the range, so the missing last candidate is never the one that mattered.

wrong$$\texttt{for ans in range(0, abs(x))}$$
right$$\texttt{for ans in range(0, abs(x) + 1)}$$
⚠ Expecting break to leave both loops

The word suggests leaving the whole search, and in a single loop that is what it does, so the meaning learned first is the one that is wrong in the nested case.

wrong$$\texttt{for a in ...: for b in ...: break}\;\Rightarrow\;\text{outer loop ends}$$
right$$\texttt{for a in ...: for b in ...: break}\;\Rightarrow\;\text{only the b loop ends}$$
⚠ Testing for equality instead of closeness

Every earlier search ended on an equals sign, and the habit carries over.

wrong$$\texttt{while ans ** 2 != x}$$
right$$\texttt{while abs(ans ** 2 - x) >= epsilon}$$
⚠ A step bigger than the window

Both numbers are small, so they feel interchangeable, and the two names are never compared in the code.

wrong$$\varepsilon=0.01,\ \texttt{step}=0.1\ \Rightarrow\ \text{no candidate inside}$$
right$$\texttt{step}\ \text{below the window, for example}\ \varepsilon^2$$
⚠ Leaving out the second exit

With a small enough step the search does succeed, so the missing exit is invisible until the day the step or the target changes.

wrong$$\texttt{while abs(ans ** 2 - x) >= epsilon}$$
right$$\texttt{while abs(ans ** 2 - x) >= epsilon and ans * ans <= x}$$
⚠ Bracketing with high = x when x is below 1

For every target above 1 the root is below the target, so the bracket looks obviously right, and nobody tests a fraction until the lab does.

wrong$$\texttt{high = x}\ \text{with}\ x=0.25$$
right$$\texttt{if x < 1: high = 1.0 else: high = x}$$
⚠ Moving the wrong end

The two lines look symmetrical, and the condition reads the guess is too small, which is easy to attach to the wrong end in a hurry.

wrong$$\texttt{if ans ** 2 < x: high = ans}$$
right$$\texttt{if ans ** 2 < x: low = ans}$$
⚠ Recomputing the midpoint before moving an end

The midpoint line looks like setup, so it drifts to the top of the loop body, where it computes the same value the pass has just tested.

wrong$$\texttt{while ...: ans = (low+high)/2;\ test;\ move end}\ \text{with the guess stale}$$
right$$\texttt{while ...: test;\ move end;\ ans = (low+high)/2}$$
⚠ Ending a loop on equality between floats

The target looks reachable, and with an exact step such as 0.25 it really is, so the habit survives every test until the step becomes a tenth.

wrong$$\texttt{while total != 1.0}$$
right$$\texttt{while abs(total - 1.0) >= 0.0001}$$
⚠ Believing the printed value is the stored value

Python prints 0.1 for a value that is not a tenth, which is helpful almost always and misleading exactly when the comparison matters.

wrong$$\texttt{print(0.1)}\rightarrow\texttt{0.1}\ \Rightarrow\ \text{stored value is a tenth}$$
right$$\texttt{format(0.1, '.20f')}\rightarrow\texttt{0.10000000000000000555}$$
⚠ Using int where rounding was meant

Both shorten a number, and on a value like 8.999 the two differ by a whole unit, which is exactly where marks are lost.

wrong$$\texttt{int(8.999999999999998)}\rightarrow\texttt{8}$$
right$$\texttt{round(8.999999999999998)}\rightarrow\texttt{9}$$
⚠ Starting the Newton search at zero

Every other search on this page starts at zero, so the habit is already formed by the time this one arrives.

wrong$$\texttt{guess = 0.0}$$
right$$\texttt{guess = k / 2.0}\ \text{or any nonzero start}$$
⚠ Dividing by the guess instead of twice the guess

The slope of the guess squared is twice the guess, which is the one fact here that has to be remembered rather than read off the code, and halving it still converges slowly enough to look plausible.

wrong$$\texttt{guess - (guess ** 2 - k) / guess}$$
right$$\texttt{guess - (guess ** 2 - k) / (2 * guess)}$$
⚠ Reusing the square-root step for a cube root

The shape of the line is memorable and the slope is not, so the 2 gets carried over to a problem where the slope is three times the guess squared.

wrong$$\texttt{guess - (guess ** 3 - k) / (2 * guess)}$$
right$$\texttt{guess - (guess ** 3 - k) / (3 * guess ** 2)}$$
Formula card
Method 3.1: exhaustive enumeration, or guess and check
$$\boxed{\texttt{ans = 0};\quad\texttt{while ans ** 3 < abs(x): ans = ans + 1};\quad\texttt{if ans ** 3 != abs(x): no answer}}$$

The answer has to be among the candidates, and the test after the loop is part of the method.

Rule 3.2: a counted search has two endings and one name
$$\boxed{\texttt{for ans in range(0, n + 1): if test: break};\quad\texttt{then ask whether ans really passes}}$$

The range must reach the last candidate; after the loop the variable holds the last value taken.

Rule 3.3: an approximate search needs two numbers you choose
$$\boxed{\text{accept ans when}\ \lvert \texttt{ans}^2 - x\rvert < \varepsilon,\ \text{step while}\ \texttt{ans}^2 \le x}$$

The step has to be smaller than the window epsilon opens, and the loop needs a second exit.

Rule 3.4: bisection search
$$\boxed{\texttt{ans} = \frac{\texttt{low}+\texttt{high}}{2};\quad \texttt{ans}^2 < x \Rightarrow \texttt{low} = \texttt{ans};\quad \text{else}\ \texttt{high} = \texttt{ans}}$$

low and high must bracket the answer before the loop starts; for a target below 1, high starts at 1.0.

Rule 3.5: never compare two floats with ==
$$\boxed{\texttt{a == b}\ \text{for floats}\ \longrightarrow\ \texttt{abs(a - b) < epsilon}}$$

Whole numbers are exact, so this replacement is only needed when a decimal point is involved.

Rule 3.6: the Newton-Raphson step for a square root
$$\boxed{\texttt{guess} \leftarrow \texttt{guess} - \frac{\texttt{guess}^2 - k}{2\,\texttt{guess}}}$$

The starting guess must not be zero, and the slope used has to match the height being driven to zero.

Check yourself

Close the page and write, from memory: the four search methods with the pass count each needed for the square root of 25; the line every search needs after its loop; the test that replaces a == b for floats; the bracket a bisection needs for a target below 1; and the Newton step. Then write an eight-line program that reads a positive whole number and reports its whole-number square root or the two neighbours.

  • Write a guess-and-check loop for a whole-number cube root, say what the candidate holds when the loop stops, and write the line that decides between found and not found?

    c-exhaustive

  • Rewrite that search as a counted loop with a break, and say what the loop variable holds after each of the two possible endings?

    c-for-search

  • Say what epsilon and step each control, work out roughly how many passes a given step costs, and explain how a step larger than the window makes a solvable problem report failure?

    c-approx

  • Trace three passes of a bisection in three columns, give the width after n passes, and fix the bracket for a target below 1?

    c-bisection

  • Say what 0.1 + 0.2 prints and why, and rewrite a loop that ends on total != 1.0 so that it ends?

    c-floats

  • Apply the Newton step twice by hand from a given start, and name the one starting value that stops the program with an error?

    c-newton

Glossary (14 terms)
exhaustive enumerationkapsamlı arama

Generating every candidate in order and testing each one until one passes or the candidates run out. It only works when the answer is among the candidates, and it needs a test after the loop to say which of the two endings happened.

guess and check

The everyday name for exhaustive enumeration in this chapter: a loop that proposes a value and tests it, rather than computing the answer directly.

approximate solutionyaklaşık çözüm

An answer accepted because it is within a chosen distance of the exact one rather than equal to it. The distance is named epsilon and the choice of it is part of the program.

epsilon

The width of the acceptance window: a guess is good enough when the difference between what it gives and what was wanted is below this number. Smaller epsilon means a better answer and more passes.

step sizeadım büyüklüğü

The gap between one candidate and the next in a search over values with decimal points. It has to be smaller than the acceptance window, or every candidate can miss it.

bisection searchikiye bölme aramasi

A search that keeps the answer between two ends, tests the midpoint, and moves one end to it, so that half of what is left is discarded on every pass. It needs the answer to be bracketed before the loop starts.

bracket

A pair of values known to have the answer between them. In this chapter the pair is called low and high, and a bracket that misses the answer produces a loop that cannot succeed.

Improving a guess by dividing how far it is wrong by the slope at that guess and subtracting the result. For a square root of k the step is the guess minus the guess squared minus k over twice the guess.

The general name for any method that repeats a step which turns one guess into a better one, until the guess is good enough. Both bisection and the Newton step are of this kind.

kayan noktalı sayı

A number with a decimal point as the machine stores it: a binary fraction with a fixed number of digits. Most decimals have no exact form, so what is kept is the nearest available value.

binary fraction

A value that can be written as a sum of halves, quarters, eighths and so on. A quarter is one, a tenth is not, which is why 0.25 behaves and 0.1 does not.

rounding erroryuvarlama hatası

The difference between the value meant and the value stored or computed. It is usually in the sixteenth digit and it accumulates as a loop runs, which is why long searches produce long tails.

overshoot

The one extra pass a loop takes because its condition is only asked again at the top. It is why a search reports a candidate one past the answer, and why a growth loop reports a balance above its target.

integer square root

The largest whole number whose square is at most a given number. It is a whole-number answer, so the search for it can use == and needs no epsilon anywhere.

What comes next
§04 · Functions, Scoping, and Abstraction (Chapter 4)

Every program here is a script that runs straight through, and the last few repeat themselves: the same five lines of bisection appear three times with a different target. The next section gives that block a name and parameters, so finding a square root becomes one line written once. The searches do not change; they stop being copied, and the name brings a second question with it, about which parts of a program can see which names.

Sources
  • kitapJohn Guttag, Introduction to Computation and Programming Using Python, with Application to Understanding Data, second edition, chapter 3 The syllabus names this chapter for the week. Its order, enumeration, then approximate solutions with bisection, then a note on floats, then Newton-Raphson, is the order of this page. The third edition is also accepted on the course and covers the same material.
  • ders malzemesiThe course's own lecture slides for this week Used for the boundary of what counts as covered: the while and for statements, nested loops, break, and then exhaustive enumeration and bisection search as the chapter's two named techniques. The slides credit an MIT introductory course as their source.
  • ders malzemesiThe lab sheets for the first lab sessions of the term Used only for the shape of an exercise, that is, a named script plus a Sample Run given character for character. No lab question is reproduced here; every exercise on this page is a different problem measuring the same skill.
  • ders malzemesiOne past midterm paper for this course, with its solutions Used for the weight and the shape of the tracing question and for the list of calls printed on the closed-book cover sheet. Measured on one paper only, so it is quoted as one observation rather than as a rule.
  • sabitThe Python 3 language reference and its library documentation Used to check the behaviour of `round` on halfway values, what `format` does to the last place, and which error type a division by zero raises.

Spotted something missing or wrong? tell us · share your own notes or an old exam.

Last updated .