• No results found

A fundamental function is like an old, comfortable pair of slippers. You never pay much attention to it until it turns up missing. If you’re doing some calculations and want a square root or sine function, you simply press the appropriate key on your calculator, and there’s the answer. If you write pro-grams in a high-order language like C, C++, Pascal, or Ada, you write sqrt(x) or sin(x), and that’s the end of it. You get your answer, and you probably don’t much care how you got it. I mean, even a three dollar calcu-lator has a square root key, right? So how hard could it be?

Not hard at all, unless, like your slippers, you suddenly find yourself in a situation where the fundamental functions aren’t available. Perhaps you’re writing in assembly language for a small microcontroller or you’re program-ming using integer arithmetic to gain speed. Suddenly, the problem begins to loom larger. Because the fundamental functions are … well … fundamental, they tend to get used quite a few times in a given program. Get them wrong, or write them inefficiently, and performance is going to suffer.

Most people don’t make it their life’s work to generate fundamental functions — we leave that to the writers of run-time libraries — so it’s diffi-cult to know where to turn for guidance when you need it. Most engineer-ing or mathematical handbooks will describe the basic algorithms, but sometimes the path from algorithm to finished code can be a hazardous one.

If you implement the algorithm blindly, without understanding its capabili-ties and limitations, you could end up in big trouble. I once wrote a real-time program in assembly language for a 10MHz Z-8000. Among many other calculations, it had to compute a sine, cosine, square root, and arctan-gent, all within 500 microseconds. Believe me, getting those functions right was important!

The whole thrust of this book is the reduction of math theory to practi-cal, useful software suitable for embedded applications. In this chapter, I will begin to look at the fundamental functions, beginning with the square root. I’ll look at the fundamental basis for its computation, assuming that you’re programming in C++ and using floating-point arithmetic. I’ll exam-ine the practical problems associated with the implementation, and see what you have to do to reduce the algorithm to practice in useful software. Then I’ll show you how to optimize the algorithm for maximum performance.

Finally, for those concerned with speed, I’ll show you how to implement the same algorithm in integer arithmetic. Subsequent chapters will address other fundamental functions.

I’ll begin with what surely must be the most fundamental of all the so-called fundamental functions: the square root. At least once a month, some-one will post a message on CompuServe’s Science and Math forum asking how to compute a square root. The answer is always the same: Newton’s method. You’ll find the formula in any handbook.

[4.1]

where a is the input argument for which you want the square root, and x is the desired result. The equation is to be applied repetitively, beginning with some initial guess, x0, for the root. At each step, you use this value to calcu-late an improved estimate of x. At some point, you decide the solution is good enough, and the process terminates.

Note that the formula involves the average of two numbers: a guess, x, and the value that you get by dividing x into the argument. (Hint: don’t start with a guess of zero!) If you have trouble remembering the formula, never fear; I’m going to show you how to derive it. You can take this lesson, one of the best lessons I learned from my training in physics, to the bank:

It’s better to know how to derive the formula you need than to try to memorize it.

45

To derive Newton’s method for square root, I will begin with an initial estimate, x0. I know that this is not the true root, but I also know, or at least hope, that it’s close. I can write the true root as the sum of the initial esti-mate plus an error term.

[4.2]

This is, you’ll recall, the true root, so its square should equal our input, a.

From Equation [4.2], this is

or [4.3]

Don’t forget that the initial estimate is supposed to be close to the final root.

This means that the error d is hopefully small. If this is true, its square is smaller yet and can be safely ignored. Equation [4.3] then becomes

,

which I can now solve for d, or more precisely, an estimate of it.

[4.4]

Now that I have d, I can plug it into Equation [4.4] to get an estimate of x.

Simplifying gives

Dividing both numerator and denominator by x0 gives .

This formula is equivalent to Equation [4.1]. The squiggly equals sign shows that this is an approximation to x. It is not exact, because I left out the d2term in the derivation. This, in turn, implies that I must keep applying the formula until the function converges on a satisfactory solution. This process is called iteration.

The previous statement seems like a simple one, but its implications can be vast and opens up lots of interesting questions, like

• How many times should I iterate?

• How do I know when to stop?

• How do I know the process will converge?

• How long before I reach the true root?

Answering the last question first, the theoretical answer is, forever. Each iteration gives us, at best, only an approximation to the root. Like the per-son in Zeno’s Paradox, who moves half the distance to the goal at each step, I’ll never actually quite get there. In practice, however, I can get arbitrarily close. In computer arithmetic, Newton’s method will eventually get within the precision of the computer word, which means that no further improve-ment is possible. I will represent the root to its closest possible value, which is all I can ever hope for, and the process will converge on the true root, for all practical purposes.

To demonstrate Newton’s method, I’ll compute the square root of two, and I’ll start with the rather horrible first guess of four. Table 4.1 gives the results.

Related documents