Round_Error.mws
AN EXAMPLE OF ROUND-OFF ERROR
Bruno Guerrieri
bruno.guerrieri@famu.edu
Florida A&M University
The following is in no way an original idea. I heard about it, many years ago, from my major professor, Dr. Christopher Hunter (Florida State University), and, at a later date, have come across essentially the same idea in "Computer Methods for Mathematical Computations" by G.E. Forsythe, M.A. Malcolm, C.B. Moller, Prentice-Hall (1977), page 16. The results you will be getting on your machine will, perhaps, be different from ours, due to different machine accuracy, but we hope that our presentation will remain consistent.
The quantity "alpha" coming up is an INTEGER. We have only used it with the values 25, 30, and 40. You are welcome to experiment but at your risks and perils!
Consider the following integral
> |
Quest := Int(x^alpha/(x+8),x=0..1);
|
Before we evaluate this integral, let us consider a string of inequalities that could be unnerving but the exercise has some value:
which can be shown to be less than 1/7 using a variation of the triangle inequality such
and using the fact that x is between 0 and 1. This will help us to realize that some upcoming answers cannot be correct.
Maple probably thinks nothing of evaluating the integral!
> |
Quest = int(x^alpha/(x+8),x=0..1);
|
> |
Quest = evalf(int(x^alpha/(x+8),x=0..1));
|
Sometime, depending on the value of alpha, the above answer comes up negative. A little bit worrisome in that case since the integral should come out as a positive number!
Plotting the (positive for
x between 0 and 1) integrand would confirm that. It is because of these negative answers and also with the upper bound seen earlier that we are very leary of an answer such as the one above.
> |
plot(x^alpha/(x+8),x=0..1);
|
What about finding the antiderivative and then applying the Fundamental Theorem of Calculus? Would we get the same answer?
> |
antideriv := int(x^alpha/(x+8),x);
|
We are not sure we expected the answer to be what it is, so let us verify it by calculating the derivative, in the hope of recovering the original integrand.
> |
simplify(diff(antideriv,x));
|
> |
evalf(subs(x=1,antideriv)) - evalf(subs(x=0,antideriv));
|
Should we check using numerical integration?
Since we have, so far, three distinct different answers. We could forge ahead with a different method, a longer and less intuitive one, but one that we can keep an eye on as we proceed. Let us define the function
=
and note that
=
=
=
=
Since
=
= ln(9/8), we could "recursively" apply the formula
up to n = alpha.
Let us solve the recursion relation symbolically, using the power of Maple (cf. Heck's "Introduction to Maple",Springer-Verlag, (2nd Ed.))
> |
Recurrence := rsolve({A(n) = -8*A(n-1) + 1/n,A(0) = ln(9/8)},A(n));
|
> |
evalf(subs(n=alpha,Recurrence));
|
We obtain an answer we already came across before but this is not to be taken as a confirmation. Let us, for verification purposes, also evaluate the recursion formula numerically
> |
for n from 1 to alpha do Ans := evalf(-8*Ans + 1/n); od:
|
Well, enough wild-goose chases! We are not quite sure what Maple is doing with respect to the integration process we carried out at the beginning but, as far as the recursion process is concerned, what could possibly have gone wrong? Well, you guessed it. Round-off errors swallowed us! But how? Well, you recall the initial value A(0) = ln(9/8)? This number was not quite the number that was stored in the variable "Ans", a few lines above, since we are not working with infinite precision here. In fact, let us run the following procedure (borrowed from Marron's "Numerical Analysis", MacMillan (2nd Ed.)) which will tell us the level of precision of our (and your) machine.
> |
Machine_precision := proc()
|
> |
local k,u,test;
u := 1; test := 1 + u;
|
> |
for k from 1 to 100 while test-1 <> 0 do
|
The above calculation (if you dissect it) was adding to the number 1 the quantity
and, by the time we were adding
for our machine, we could not tell the difference (of course, the number may be different for your machine and everything we are saying from now on will have to take this into account). Another way to put it is to say that the quantity that our computer stored for ln(9/8) was roughly off by
. After that, this rather minute error was AMPLIFIED repetitively, everytime we multiplied the "A(n) at bat" by a factor of 8. A quick calculation, trying to measure the "evolution" of that error (we are multiplying by 8, alpha many time) tells us the following:
> |
Error := evalf(2^(-32)*8^(alpha));
|
Here, let us make a feeble attempt at saying that the value for "Ans" and "Error" are within the same ballpark (in absolute value and as we could expect) but you probably will not be convinced. And anyway, the more important question is whether we will ever obtain the right answer, or, at least, a satisfactory one! Well, here is the grand finale. We are actually going to make the roundoff error work for us(!!!). Let us revisit the recursion formula seen earlier and solve it for
. We obtain
. All we have to do now is start at, say A(2*alpha) and work our way DOWN to A(alpha). Think of alpha as being 30 when you are reading this.
Excuse me, but what is A(2*alpha) equal to, may we ask? Here is the beauty of the whole thing. In a way, the value that A(2*alpha) assumes almost does not matter because we will be dividing the possible error of a wild guess by a factor of 8 at each round! So, for simplicity, let us assume that A(2*alpha) = 1000.0. Here we go! if you wish to start further up the ladder for as better clean-up, then change the value of "start" to that "higher up" choice.
> |
Descending_Iterate[start] := 1000.0:
|
> |
for n from 1 to start-alpha+1 do
|
> |
Descending_Iterate[start-n] := - Descending_Iterate[start-n+1]/8 + 1/(8*(start-n+1)):
|
> |
NewTable := [seq([start-n+1,Descending_Iterate[start-n+1]],n=1..start-alpha+1)]:
|
> |
array(1..start-alpha+1,1..2,NewTable);
|
Compare that with an earlier answer:
So, now we know what the exact answer to 10s ( maybe, depending on the parameter alpha you use) is!