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.
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 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:
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.
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.
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
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.
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.
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
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.
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.
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.
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.
Predict the printed value of a float computation, and replace an equality test between floats with a test that works.
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
covered
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.
covered
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.
off syllabus
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 = 0for i inrange(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
In [1]: %run untitled0.py
Three values, not four: 11 would be next but it is not below 10. The reason this matters on this page is that a search written over a range has to include its last candidate, which is why the searches here are written range(0, n + 1).
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.
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.
int on a float cuts the fraction off towards zero.
format(v, '.3f') hands back characters, rounded to three places.
IPython console
In [1]: %run untitled0.py
The middle line is the one worth staring at: int cut 3.99 down to 3 rather than rounding it to 4, and that single behaviour is what wrecked the cube-root shortcut at the top of this page. The last line also shows format rounding the characters, since 0.6666... to three places is 0.667 and not 0.666.
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.
Check the remainder against the division: 2 times 3 plus 1 is 7, which is what // and % together always promise for whole numbers.
Notation
symbol
reads as
means
watch 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 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.
The candidates are walked from the left and their cubes are compared with the target. The loop stops at the first candidate whose cube is not below the target, which is the only candidate that needs to be tested for equality afterwards.
Looks like this, but is not
Start, condition, body: the same three parts.
x = 8
ans = 0
passes = 0while ans ** 3 < abs(x) and passes < 4:
print('testing', ans)
passes = passes + 1print('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.
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 = 0while ans ** 3 < abs(x):
ans = ans + 1if 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 -64is -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 = 0while ans * ans < x:
ans = ans + 1
guesses = guesses + 1print('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.
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 = 0while ans ** 3 < x:
ans = ans + 1print(ans, ans ** 3)
print('stopped')
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The condition is asked before each pass, so a candidate whose cube is still below the target buys one more pass. That is why the printed list always ends one row past the target: the pass that prints 64 was bought by the pass that printed 27. Notice also that 0 never appears, because the increase happens before the print.
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.
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)}$$
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}$$
3.2The same search as a counted loop, and how it says no
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.
Two runs of the same shape of loop. In both, the name ans holds 2 or 4 after the loop; only the test written after the loop can tell which of the two endings happened.
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 = 20for ans inrange(0, 5):
if ans * ans == target:
breakprint('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 is4
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 = 8for ans inrange(0, abs(x) + 1):
if ans ** 3 >= abs(x):
breakif ans ** 3 != abs(x):
print(x, 'is not a perfect cube')
else:
print('Cube root of', x, 'is', ans)
Sample Run:
Cube root of 8is2
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 = 0for d inrange(2, n):
if n % d == 0:
smallest = d
breakif smallest == 0:
print(n, 'is prime')
else:
print('smallest divisor of', n, 'is', smallest)
print('d ended at', d)
Sample Run:
smallest divisor of 91is7
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 = 0for i inrange(1, 10, 3):
if i * i > 20:
break
total = total + i
print('i', i, 'total', total)
print('final', total, i)
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The third candidate contributes nothing to the total, because the break comes before the addition, but it does leave its value in the loop variable. So the total is 1 plus 4 and the reported candidate is 7, a pair of numbers that never appeared together inside the loop. Had the break never fired, the loop variable would hold the last value the range produced, which is also 7 here; with range(1, 12, 3) it would be 10.
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.
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.
Epsilon opens a window around the answer, and the window is narrow: one hundredth on the square is about seven thousandths on the root. The step has to be smaller than that window or every candidate misses it.
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.0whileabs(ans ** 2 - x) >= epsilon and ans * ans <= x:
ans = ans + step
print('stopped at ans =', ans)
ifabs(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
step
passes used
did 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.0whileabs(ans ** 2 - x) >= epsilon and ans * ans <= x:
ans = ans + step
guesses = guesses + 1print('guesses =', guesses)
ifabs(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 = 499904.999000000001688is 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.
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.0whileabs(ans ** 2 - x) >= epsilon and ans * ans <= x:
ans = ans + step
guesses = guesses + 1print('guesses =', guesses)
print(ans, 'is close to the square root of', x)
Sample Run:
guesses = 504.999999999999998is 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.0whileabs(ans ** 2 - x) >= epsilon:
ans = ans + 0.01print(format(ans, '.2f'))
print(format(ans ** 2, '.4f'))
print(abs(ans ** 2 - x) < epsilon)
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The accepted candidate is 3.15, whose square is 0.0775 below 10. Two things are worth noticing. The answer is accepted although it is wrong in its second decimal place, because epsilon was set on the square and not on the root. And the printed 3.15 is the result of format, not the stored value: adding 0.01 fifteen times leaves a value a shade off 3.15, which is why the raw name is not printed here.
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.
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.
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.
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.
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.
Six passes of the search for the square root of 10. The dashed line is the answer, and it is inside the blue half every single time, which is the property that makes the method safe.
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 = 0whileabs(ans ** 2 - x) >= epsilon and guesses < 30:
guesses = guesses + 1if ans ** 2 < x:
low = ans
else:
high = ans
ans = (high + low) / 2.0print('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
method
passes
answer 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.0if x < 1:
high = 1.0else:
high = x
ans = (high + low) / 2.0
guesses = 0whileabs(ans ** 2 - x) >= epsilon:
guesses = guesses + 1if ans ** 2 < x:
low = ans
else:
high = ans
ans = (high + low) / 2.0print('guesses =', guesses)
print(ans, 'is close to the square root of', x)
Sample Run:
guesses = 135.00030517578125is 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.
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 = 0whileabs(ans ** 2 - x) >= epsilon:
print('low', low, 'high', high, 'ans', ans)
guesses = guesses + 1if ans ** 2 < x:
low = ans
else:
high = ans
ans = (high + low) / 2.0print('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.
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.0for i inrange(3):
mid = (low + high) / 2print(i, low, high, mid)
if mid * mid < 8:
low = mid
else:
high = mid
print(format(low, '.3f'), format(high, '.3f'))
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The final interval is 2 to 3, one eighth of the original width of 8, which is what three halvings do. The answer, about 2.8284, is inside it, and it is inside every interval printed on the way, because a halving only ever throws away a half that cannot contain the answer. Getting the width below 0.1 needs the width to fall from 1 to under 0.1, so four more passes, since a half to the fourth is 0.0625.
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.
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.
Near three tenths only these evenly spaced values exist. The sum of a tenth and two tenths lands on the tick one step above the tick nearest 0.3, which is why the equality test says no and a window says yes.
Looks like this, but is not
Ten passes, a tenth each, stop at one. It even has a guard.
value = 0.0
steps = 0while value != 1.0and steps < 12:
value = value + 0.1
steps = steps + 1print('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 as
stored value at twenty places
exact
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.0for i inrange(10):
total = total + 0.1print(total)
print(total == 1.0)
print(abs(total - 1.0) < 0.0001)
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.
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.
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 = 0while total != 1.0:
total = total + step
count = count + 1print(count, total)
step = 0.1
total = 0.0
count = 0whileabs(total - 1.0) > 0.0001:
total = total + step
count = count + 1print(count, format(total, '.4f'))
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The first loop is safe by luck: a quarter is exactly representable, so the equality test meets its target on the nose. The second loop misses 1.0 by about a tenth of a millionth of a millionth, which the window absorbs. With the equality test instead of the window, the second loop would sail past 1.0 and never come back to it, and the program would have to be killed. Note also the printed 1.0000: format rounds the characters and hides the shortfall completely.
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.
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.
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.
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.
The step is a straight line reading of a curved problem. Follow the tangent at the guess down to the axis and the landing point is the next guess, already much nearer the answer than a halving would have managed.
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.
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
step
guess
guess 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.
guesses = 4
Square root of 24.0is 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.
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 = 0whileabs(ans ** 2 - k) >= epsilon:
b = b + 1if ans ** 2 < k:
low = ans
else:
high = ans
ans = (high + low) / 2.0
guess = k / 2.0
n = 0whileabs(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.
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.
(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
In [1]: %run untitled0.py
The first step takes the guess from 4 to 3.125, so the error falls from 1 to 0.125. The second takes it to 3.0025, so the error falls to 0.0025, which is about the square of the previous error divided by six. That is the pattern: each step roughly squares the error, so the number of correct digits doubles, and this is why four steps were enough for the square root of 24 on this page.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Guess the midpoint
ans = (high + low) / 2.0, using true division.
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.
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.
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.
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.
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.
Can the answer be bracketed
Then bisection, which turns fifty thousand passes into thirteen and barely notices when the target grows.
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 = 0whileabs(ans ** 2 - x) >= epsilon and ans * ans <= x:
ans = ans + step
passes = passes + 1print('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.
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.0for i inrange(4):
total = total + 0.25print(total)
print(total == 1.0)
Sample Run:
1.0True
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.0for i inrange(10):
total = total + 0.1print(total)
print(total == 1.0)
print(abs(total - 1.0) < 0.0001)
Sample Run:
0.9999999999999999FalseTrue
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.
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
Name the candidate and the target above the loop, and if the target can be negative compare against its size instead.
Decide what close enough means: == for whole numbers, a name epsilon and abs(difference) < epsilon for anything else.
Write the loop so that every pass makes progress, whether that is a step, an increment, or a halving of the interval.
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.
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 = 0whileabs(ans ** 2 - x) >= epsilon:
guesses = guesses + 1if ans ** 2 < x:
low = ans
else:
high = ans
ans = (high + low) / 2.0print('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.00is 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 = 0while ans ** 3 < abs(x):
ans = ans + 1if 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 -1728is -12
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.
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.
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.
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.
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 = 0while ans * ans <= x:
ans = ans + 1.0
tested = tested + 1print('Integer square root of', x, 'is', ans)
print('Passes:', tested)
Sample Run:
Enter a positive integer: 50
Integer square root of 50is8.0
Passes: 8
Read the number as a whole number.
Start the candidate at 0 and the counter at 0, above the loop.
Move to the next candidate with ans = ans + 1.0.
Keep going while the square of the candidate is at most x.
Report the candidate the loop stopped on.
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
(a) Write the program.
(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.
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 = 0while high - low > 1:
mid = (low + high) // 2if mid * mid <= x:
low = mid
else:
high = mid
steps = steps + 1print('step', steps, 'low', low, 'high', high)
print('floor of the root is', low)
for k inrange(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.
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 is4416True525False636False
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.
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.
whileabs(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.
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.
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.
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 = 45for d inrange(2, 10):
print('trying', d)
if n % d == 0:
breakprint('d =', d, 'divides?', n % d == 0)
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The last line is True because a break really happened. On a prime such as 47 nothing would break, the range would run out, the loop variable would hold 9, and the same line would print d = 9 divides? False. That final False is the only thing separating a found divisor from a failed search, which is why it belongs in the report rather than in your head.
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.
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 = 1while n // place > 0:
print(place, n // place, (n // place) % 10)
place = place * 10print('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
In [1]: %run untitled0.py
The digits come out right to left, which is what this pair of operations gives you for free. The last line shows the place one step past the number, because the condition is only false once the shift has emptied it out. There is no string anywhere here, which matters: a question can forbid converting the number to text, and then this is the shape you need.
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.
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 = 0whileabs(ans ** 2 - x) >= epsilon and ans * ans <= x:
ans = ans + step
passes = passes + 1print(passes, format(ans, '.1f'))
print(abs(ans ** 2 - x) < epsilon)
Find
(a) Write exactly what this prints.
(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
In [1]: %run untitled0.py
The second exit ended it: at 1.8 the square is 3.24, which is past the target, so the loop stops with the distance at 0.24 and the acceptance test answers False. The window on the candidate here runs from about 1.7176 to 1.7464, a width of 0.029, and a step of 0.1 cannot land in it. Eighteen passes were spent on a search that was doomed by its own step size.
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.
(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
In [1]: %run untitled0.py
After three passes the interval is 2.1250 to 2.5000, and the answer 2.2360 is inside it, as it was inside every interval printed. The width went from 3 to 0.375, one eighth, which is three halvings. Notice the bracket did not have to start at zero: any pair that contains the answer will do, and a tighter start saves passes.
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.
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 1296is6
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
(a) Write the program.
(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 113.500000
The run continues for four more step lines and then prints the answer and the count.
Find
(a) Write the program.
(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.
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 = 97for d inrange(2, n): # line 1if n % d == 0: # line 2break# line 3print('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.
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.0whileabs(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: 20482048 = 2 ** 11
Find
(a) Write the program.
(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.
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.
whileabs(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.
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
(a) Write the program.
(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.
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 = 6for a inrange(1, 4):
for b inrange(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
In [1]: %run untitled0.py
Five lines, and the first is a row line rather than a pair, because the outer value 1 finds no partner. Each pair appears before its own row line, since the inner loop finishes first. An exhaustive search over ordered pairs tests 9 to find 2, and the count is the product of the two ranges.
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.
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 is30590.23TL
Find
(a) Write the program.
(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.
(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
In [1]: %run untitled0.py
The two values usually written down wrongly are on the second line: the whole division of -7 by 2 is -4 and not -3, and the remainder is 1 and not -1. The rule tying them together is that the quotient and the remainder rebuild the number, and -4 times 2 plus 1 is -7.
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.
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)}$$
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.
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.
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.