tags:

views:

189

answers:

6

Is there a clever/efficient algorithm for determining the hypotenuse of an angle (i.e. sqrt(a^2 + b^2)), using fixed point math on an embedded processor without hardware multiply?

+3  A: 

You can start by reevaluating if you need the sqrt at all. Many times you are calculating the hypotenuse just to compare it to another value - if you square the value you're comparing against you can eliminate the square root altogether.

Mark Ransom
+3  A: 

Unless you're doing this at >1kHz, multiply even on a MCU without hardware MUL isn't terrible. What's much worse is the sqrt. I would try to modify my application so it doesn't need to calculate it at all.

Standard libraries would probably be best if you actually need it, but you could look at using Newton's method as a possible alternative. It would require several multiply/divide cycles to perform, however.

AVR resources

Nick T
You don't need to divide to approximate a square root. You can easily just use a binary-search type algorithm that only does one multiply per bit and no divides.
Gabe
+4  A: 

Consider using CORDIC methods. Dr. Dobb's has an article and associated library source here. Square-root, multiply and divide are dealt with at the end of the article.

Clifford
Note: I have used this library and found an error in the log() function. This is corrected by adding a `0x0LL` to the end of the `log_two_power_n_reversed[]` array initialiser. I have confirmed this correction with the author.
Clifford
+2  A: 

One possibility looks like this:

#include <math.h>

/* Iterations   Accuracy
 *  2          6.5 digits
 *  3           20 digits
 *  4           62 digits
 * assuming a numeric type able to maintain that degree of accuracy in
 * the individual operations.
 */
#define ITER 3

double dist(double P, double Q) {
/* A reasonably robust method of calculating `sqrt(P*P + Q*Q)'
 *
 * Transliterated from _More Programming Pearls, Confessions of a Coder_
 * by Jon Bentley, pg. 156.
 */

    double R;
    int i;

    P = fabs(P);
    Q = fabs(Q);

    if (P<Q) {
        R = P;
        P = Q;
        Q = R;
    }

/* The book has this as:
 *  if P = 0.0 return Q; # in AWK
 * However, this makes no sense to me - we've just insured that P>=Q, so
 * P==0 only if Q==0;  OTOH, if Q==0, then distance == P...
 */
    if ( Q == 0.0 )
        return P;

    for (i=0;i<ITER;i++) {
        R = Q / P;
        R = R * R;
        R = R / (4.0 + R);
        P = P + 2.0 * R * P;
        Q = Q * R;
    }
    return P;
}

This still does a couple of divides and four multiples per iteration, but you rarely need more than three iterations (and two is often adequate) per input. At least with most processors I've seen, that'll generally be faster than the sqrt would be on its own.

For the moment it's written for doubles, but assuming you've implemented the basic operations, converting it to work with fixed point shouldn't be terribly difficult.

Jerry Coffin
What does "*reasonably robust*" mean? Does that mean that it does not work in all cases!?
Clifford
There's no way that math.h and a double type is going to fit on an ATTiny. You get 4k of program space, maximum, and both division and multiplication are going to be in software. However, this would work well for a processor with a hardware multiply (or divide) instruction.
reemrevnivek
@Clifford: Almost nothing works in *all* cases, but this is considerably more robust than the typical `sqrt(x*x+y*y)` (e.g., it will work even when `x*x+y*y` would overflow). It could still underflow if (for example) `Q/P` is small enough that squaring it underflows (much more likely with fixed point than IEEE FP).
Jerry Coffin
@reemevnivek: yes, but trying to present it for an unknown fixed-point format is next to impossible. `<math.h>` is being used only for `fabs`, so its inclusion (by itself) would mean next to nothing (including a header normally only declares things; only what you *use* goes in the executable). Ultimately you're right though: as the final sentence makes clear, I certainly wouldn't expect to use this as-is on a micro-controller.
Jerry Coffin
@Jerry: Agreed; I merely thought that "reasonably robust" without qualification or quantification was a bit weak as a comment, and would require analysis on the part of the user to determine the consequences of using this code. It may have meant that it would fail in non-deterministic ways rather than just for a deterministic range of inputs, or it may simply refer to its precision in all cases rather than its correctness in some cases. The comment alone would ring alarm bells for example if this were a safety-critical application.
Clifford
+1  A: 

Maybe you could use some of Elm Chans Assembler Libraries and adapt the ihypot-function to your ATtiny. You would need to replace the MUL and maybe (i haven't checked) some other instructions.

Juri Robl
+8  A: 

If the result doesn't have to be particularly accurate, you can get a crude approximation quite simply:

Take absolute values of a and b, and swap if necessary so that you have a <= b. Then:

h = ((sqrt(2) - 1) * a) + b

To see intuitively how this works, consider the way that a shallow angled line is plotted on a pixel display (e.g. using Bresenham's algorithm). It looks something like this:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |*|*|*|    ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    |
| | | | | | | | | | | | |*|*|*|*| | | |    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    |
| | | | | | | | |*|*|*|*| | | | | | | | a pixels
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    |
| | | | |*|*|*|*| | | | | | | | | | | |    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+    |
|*|*|*|*| | | | | | | | | | | | | | | |    v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 <-------------- b pixels ----------->

For each step in the b direction, the next pixel to be plotted is either immediately to the right, or one pixel up and to the right.

The ideal line from one end to the other can be approximated by the path which joins the centre of each pixel to the centre of the adjacent one. This is a series of a segments of length sqrt(2), and b-a segments of length 1 (taking a pixel to be the unit of measurement). Hence the above formula.

This clearly gives an accurate answer for a == 0 and a == b; but gives an over-estimate for values in between.

The error depends on the ratio b/a; the maximum error occurs when b = (1 + sqrt(2)) * a and turns out to be 2/sqrt(2+sqrt(2)), or about 8.24% over the true value. That's not great, but if it's good enough for your application, this method has the advantage of being simple and fast. (The multiplication by a constant can be written as a sequence of shifts and adds.)

Matthew Slattery
That's quite a clever approximation.
caf
Nice; but you should probably mention to take the absolute value of both a and b as well. +1 for some brief error analysis
Nick T
@Nick T: good point, edited accordingly - thanks.
Matthew Slattery