Lab 2: Errors
In Lab 1, we discussed the represenation of binary floating point numbers. One of the key results we discussed was the fact that there are only a finite number of bits, usually 64, used to store a floating point number.
Today, we're going to explore the often surprising consequences of this fact. First, we'll recall how digits are stored: in an IEEE single precision (32 bit) float, a number is represented as
where the bias . For example,
| s | e | f |
|---|---|---|
| 0 | 10000010 | 01000000000000000000000 |
What decimal number does this represent? Work it out on paper!
Adding Floating point numbers
To begin to see how error creeps in just from our finite floating point representation, let's consider adding two numbers in regular, decimal scientific notation with bias . If we had say , we can easily see the answer is . If we use double precision numbers (as python does by default), this poses no challenge to the computer either:
However, let's consider doing the same thing, but now we'll assume we have only 4 digits of precision in the mantissa of our numbers. That gives us and . So far, so good. Floating point allows us to represent both of these quantities with the same number of digits of precision.
When we go to add them, the computer will do that by shifting the smaller number such that it has the same exponent of as the larger number, in this case . Remember, we can represent the same number many ways--but we must keep only 4 digits of precision:
You can see where this is going. We shift one more time, and we end up with . This is not good, since this is zero, and we still have one more shift to do! We end up with
= which is clearly the wrong answer.
Machine Precision
This leads us to a very important definition: there exists a number such that , where that subscript means "as represented on the computer" rather than "the mathematical object 1".
Let's find an approximation for experimentally. In doing so, we're going to meet another kind of loop, called the while loop. A while loop tests an expression at the top, just like an if statement. If the expression is true, then the loop body executes. If it is false, the loop stops. Be careful! While loops may never stop! This is sometimes what you want, but often not. You must make sure that the expression at the top changes!
hint
If your while loop seems to never end, you can go to the Kernel menu in the Jupyter notebook and choose Interrupt Kernel. That should cause your code to stop and an error message to be printed. You can then fix your loop and restart. Any calculation like these ones should be more or less instantaneous. Later in the class, we'll have calculations that will take longer.
So what does this code do? How does it find epsilon?
That expression above was using standard python floating point numbers, which are double precision. We can use numpy to create single precision numbers. Here's how:
Note we have to be careful! Every number in python is double precision unless you say otherwise. Also, python is smart enough to automatically "promote" a variable like eps to double precision if you add, subtract, multiply, or divide by something of double precision. That's why I've carefully set all numbers to be np.float32s in the above cell.
Questions
What is the difference in between single and double precision? Try to explain what is going on in terms of what you've learned about floating point numbers, comparing them to numbers written in scientific notation with a limited precision.
Subtractive cancellation
By hand, subtract from . How many digits of precision did you start with? How many did you end with? What does that tell you about your new answer? Think about these questions for a minute before proceeding.
This is called subtractive cancellation: you are cancelling off all of your more precise digits, and leaving only the least precise one (in this case). The subtraction you compute may be exact itself, but if there is any error in the starting values ( and ) it becomes amplified. The technical term for this is "bad."
Example 1
Calculate for with . Be careful: you want to be a float, not an int. To do that make sure you use 10.**i Store the answers in a numpy array called y1.
Now do the same thing, but get rid of the subtraction. Multiply by
You should get a new expression for with no subtractions. Now, using your new formulation, calculate again, and store it in a new numpy array y2.
Which one of these is more accurate? If it's not obvious, you probably made a coding mistake.
The less accurate one is called unstable. The errors caused by subtractive cancellation grow as you move to certain values of , in this case, large values.
Now using pyplot, plot the answer with subtraction y1 and the answer without subtraction y2 as a function of x. Code to do this is in the cell below. Look at the code and see the sytle of the plot.
What's wrong with this plot? one thig to note is that the axis clearly varies by a lot. Using a logarithmic plot is helpful here. Matplotlib allows log plots on the x, y, or both axes easily:
But even if you try that you might still not be able to see a clear difference between them. How could you fix this? hint: it's something you learned in 107, if not before that
Fix it, and show a plot that displays how much worse one solution is than the other. Make sure to label all axes on the plot!
Example 2
You need to compute the following
for .
Your first thought is to simply solve the equation for a set of , something like , where and . Don't use ! Ask yourself why not before continuing.
First, set up your variable as a numpy array called theta. Make sure the values get closer to . Try experimenting with different values of .
Now that you have theta, compute using the above formula.
Now, use your knowledge of trigonometry to find an equivalent form for . Hint: it's a single trig function
numpy has that function built-in. Run it on your theta array, and print the difference between your computed y and the answer given by the numpy function. This is called the "absolute error", .
Now compute the relative error (you may have called this the "percent error" before). .
Finally, plot the relative error as a function of .
Very important note: The true solution is often unknown, and in any real problem is totally unknown. If you knew the true solution, you wouldn't be bothering to run a computer simulation! It's very important for you to remember that the built in numpy function you just calculated is also an approximation, but in this case, it's a much, much better approximation.
Every computational answer is wrong, but some wrong answers are useful!