Where it falls apart: catastrophic cancellation
Run the column further and the answers get worse, then silly, then exactly zero. The machine, not the mathematics.
The loop in the last lesson stopped at , and it stopped there on purpose. The algebra says the quotient is , so smaller steps should give better answers forever. Take the same loop out to and something happens that the algebra gives no warning about at all.
Read down to the last row before you change anything. Then set a = 1.0 and run it again: the collapse still happens, at a slightly different place.
| Step h | Quotient | What it should be |
|---|---|---|
| 1e-7 | 6.000000087880 | 6.0000001 |
| 1e-8 | 5.999999963535 | 6.00000001 |
| 1e-9 | 6.000000496442 | 6.000000001 |
| 1e-10 | 6.000000496442 | 6.0000000001 |
| 1e-12 | 6.000533403494 | 6.000000000001 |
| 1e-14 | 6.217248937901 | 6.00000000000001 |
| 1e-15 | 5.329070518201 | 6.000000000000001 |
| 1e-16 | 0.000000000000 | 6.0000000000000001 |
Three separate things are visible there. Around the answers stop getting better. Below they get actively worse, and by the first decimal place is wrong. At the answer is exactly zero, reported with no complaint of any kind. If you had trusted the loop, you would have concluded that the ball was standing still.
A double holds about sixteen digits and no more
Nearly every language stores a number with a decimal point in it as a double: 64 bits, of which 52 are given over to the digits of the value and the rest carry its sign and its size. There are 53 digits in play rather than 52, because the leading digit of a binary number is always 1 and so never has to be stored. Fifty-three binary digits works out at roughly 15 to 17 significant decimal digits. Not 15 to 17 decimal places, note; 15 to 17 significant digits, counted from the first one that is not a zero.
Anything beyond that is not stored inaccurately. It is not stored. The value is rounded to the nearest number the format can represent and the rest is gone, permanently, before your next line of code runs.
Change 1e-16 to 1e-17, then to 1e-14. Somewhere between those, adding the number starts to make a difference. Find roughly where.
The second line is the famous one, and it is worth understanding rather than memorising: 0.1 and 0.2 are not exactly representable in binary any more than a third is exactly representable in decimal, so their stored versions are slightly off and their sum is slightly off in a way that does not happen to match the stored version of 0.3.
The third line is the one that matters here. Nine plus a ten-thousandth of a trillionth needs seventeen significant digits to write down. The format has sixteen. So the sum is rounded, and it rounds back to exactly 9.
The subtraction is where the digits die
Now watch what that does inside the difference quotient. With and , the rounding happens at a + h, before the squaring ever runs. The sum you asked for is 3.00000001, and the nearest double to it is 3.000000009999999939, so a little has gone already. Squaring that stored value gives 9.000000059999999635, where the true is 9.0000000600000001.
a + h wanted 3.000000010000000000
stored 3.000000009999999939 rounded already
f(a + h) stored 9.000000059999999635 the stored a + h, squared
f(a) stored 9.000000000000000000 exact, nothing to round
--------------------
difference 0.000000059999999635 eight good digits, then noiseSixteen digits went into the subtraction and eight came out. The leading nine and the seven zeros after it were identical in both numbers, so they cancelled to nothing, and the digits that would have replaced them at the bottom end were thrown away when the values were stored. Dividing by h scales the survivors back up: 0.000000059999999635 over is 5.999999963535, which is the row in the table. The exact answer is 6.00000001, so the scaling has enlarged the noise along with the answer.
This is called catastrophic cancellation: subtracting two numbers that are nearly equal, so that the significant digits cancel and what is left is short, and made mostly of whatever rounding error was already there. The word catastrophic is doing real work. The subtraction itself is computed perfectly; the loss happened earlier, when the operands were rounded, and the subtraction merely exposes it.
Look only at the a + h line for k = 16 and k = 18. Then change a to 0.001 and run it again: with a small starting point there is room for more digits, and the collapse moves.
At the line reads a + h = 3.0. The step was absorbed entirely: 3 + 1e-16 rounds to 3, so the top of the fraction is a number minus itself, which is exactly zero, and zero divided by anything is zero. The program is not confused and does not warn you. It has computed exactly what you asked, and the answer is worthless.
Two errors, pulling opposite ways
There are two sources of error in that column and they behave in opposite directions, which is why the total has a floor rather than heading to nothing.
| Error | Where it comes from | As h shrinks |
|---|---|---|
| Mathematical | The quotient is 6 + h, and you want 6. The h is the error. | Shrinks in proportion to h |
| Rounding | Cancellation leaves a tiny absolute error on top, then you divide by h. | Grows in proportion to 1 over h |
The rule of thumb is worth remembering, because it explains the shape of the column exactly. The smallest relative step a double can resolve is about . The best h is roughly the square root of that, about , and at that step you get about half the digits: eight of them. That is the row where the answers stopped improving.
You compute the difference quotient of at with . What is the exact answer you are aiming at, and roughly how many of its digits should you expect to get right?
The exact difference quotient is . About eight significant digits should survive, so an answer like 5.999999963 is exactly what the rule of thumb predicts: right to eight digits, noise after that. Asking for the ninth digit of a difference quotient computed this way is asking for something the arithmetic cannot supply.
So the experiment has a ceiling. A program can take h down to about and no further, which means no program can ever finish the journey to . If the value 6 is going to be established, something other than a loop has to establish it, and that something is the subject of the next lesson.