3

I'm trying to evaluate a^n, where a and n are rational numbers. I don't want to use any predefined functions like sqrt() or pow()

So I'm trying to use Newton's Method to get an approximate solution using this approach:

3^0.2 = 3^(1/5) , so if x = 3^0.2, x^5 = 3.

Probably the best way to solve that (without a calculator but still using the basic arithmetic operations) is to use "Newton's method". Newton's method for solving the equation f(x)= 0 is to set up a sequence of numbers xn defined by taking x0 as some initial "guess" and then xn+1= xn- f(xn/f '(xn) where f '(x) is the derivative of f.

Posted on physicsforums

The problem with that method is that if I want to compute 5.2^0.33333, I'll need to find the roots for this equation x^10000 - 5.2^33333 = 0. I end up with huge numbers, and get inf and nan errors most of the time.

Can someone give me advice on how to solve this problem? Or, can someone provide another algorithm to compute a^n?

user3787097
  • 181
  • 2
  • 12
  • Is there a *reason* you don't want to use the standard math functions? Any other approach is likely to be considerably slower and less precise. – R.. GitHub STOP HELPING ICE Jul 04 '14 at 03:28
  • 2
    @R.. There's not a good reason to do it like that. I'm just having fun, and seeing what can I do. This is not a homework or somethig. I'm out of college, programming is a hobby. But I can think, that if I'm porgramming a primitive micocontroller, it may not have those functions. – user3787097 Jul 04 '14 at 03:30
  • How did you come up with `3.2 = 3^(1/5)`? – R Sahu Jul 04 '14 at 04:12
  • @RSahu That was a mistake, corrected now. – user3787097 Jul 04 '14 at 04:15
  • @R Sahu Hmmmm "Newton's method ... but not to evaluate ... sqrt(x)". Best way to motivate an engineer is to say something can't be done. http://en.wikipedia.org/wiki/Newton's_method – chux - Reinstate Monica Jul 04 '14 at 04:30
  • @RSahu Of course it can! I encourage you to google the maths. I have already done it, unfortunately, it seems to be a bad approach for programming. – user3787097 Jul 04 '14 at 04:30
  • 1
    evaluating 5.2^0.001 means solving x^1000 = 5.2. then t1 = x^3 gives you 5.2^0.003. t2 = t1^10 = x^30 gives you 5.2^0.03. t3 = t2^10 = x^300 gives you 5.2^0.3. finally t1*t2*t3 gives you 5.2^0.333. that sounds like x^333. – Cheers and hth. - Alf Jul 04 '14 at 04:35
  • @user3787097: I've never seen a cpu/fpu that has a builtin, working pow operation. `pow` is a very complex library function (probably the most intricate and arcane of all the math library functions) that's difficult to duplicate and completely impractical as a single hardware operation. – R.. GitHub STOP HELPING ICE Jul 04 '14 at 04:37
  • @Cheersandhth.-Alf very nice suggestion. But, x^1000, where x is for example 5, it's already a huge number ---> 9.33x10^698 I dont think I can use that approach. – user3787097 Jul 04 '14 at 04:44
  • I have implemented such facility and used it many times. It provides a class for storing a natural number of any conceivable size, and a class for storing a rational number of any conceivable size (using a natural numerator and a natural denominator). Both classes support any arithmetic operation provided by C++. The rational number class also implements power and root by a natural exponent. If you want to power by a rational exponent, then you can power by the numerator of the exponent and root by the denominator of the exponent. See https://github.com/barakman/Num. – barak manos Jul 04 '14 at 05:55
  • @user3787097: worked fine for me when i prepared that example. no problem. – Cheers and hth. - Alf Jul 04 '14 at 06:30
  • If the exponent can be represented as a fraction: upper/lower then base^(upper/lower) yields the result: base^upper / base^lower. – user3629249 Jul 06 '14 at 01:00

3 Answers3

7

It seems your task is to calculate

⎛ xN ⎞(aN / aD)
⎜⎼⎼⎼⎼⎟           where xN,xD,aN,aD ∈ ℤ,  xD,aD ≠ 0
⎝ xD ⎠

using only multiplications, divisions, additions, and subtractions, with Newton's method as the suggested method to implement.

The equation we're trying to solve (for y) is

             (aN / aD)
y = (xN / xD)            where y ∈ ℝ

Newton's method finds a root of a function. If we want to use it to solve the above, we substract the right side from the left side, to get a function whose zero gives us the y we want:

                  (aN/aD)
f(y) = y - (xN/xD)        = 0

Not much help. I guess this is as far as you got? The point here is to not form that function just yet, because we don't have a way to calculate a rational power of a rational number!

First, let's decide that aD and xD are both positive. We can do that simply by negating both aN and aD if aD was negative (so sign of aN/aD does not change), and negating both xN and xD if xD was negative. Remember, by definition neither xD or aD is zero. Then, we can simply raise both sides to the aD'th power:

 aD            aN     aN     aN
y   = (xN / xD)   = xN   / xD

We can even eliminate the division by multiplying both sides by the last term:

 aD     aN     aN
y   × xD   = xN

Now, this looks quite promising! The function we get from this is

        aD   aN     aN
f(y) = y   xD   - xN

Newton's method also requires the derivative, which is obviously

f(y)            aD   aN
⎼⎼⎼⎼ = df(y) = y   xD   y / aD
 dy

Newton's method itself relies on iterating

             f(y)
y    = y  - ⎼⎼⎼⎼⎼⎼
 i+1    i    df(y)

If you work out the math, you'll find that the iteration is just

                                 aD
                y[i]      y[i] xN
y[i+1] = y[i] - ⎼⎼⎼⎼ + ⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼
                 aD           aD   aN
                       aD y[i]   xD

You don't need to keep all the y values in memory; it is enough to remember the last one, and stop iterating when their difference is small enough.

You do still have exponentiation above, but now they are integer exponentiation only, i.e.

  aD
xN   = xN × xN × .. × xN
       ╰───────┬───────╯
              aD times

which you can do very simply, for example just by multiplying the argument by itself the desired number of times, e.g. in C,

double ipow(const double base, const int exponent)
{
    double result = 1.0;
    int    i;
    for (i = 0; i < exponent; i++)
        result *= base;
    return result;
}

There are more efficient methods to do integer exponentiation, but the above function should be perfectly acceptable for this.

The final problem is to pick the initial y so that you get convergence. You cannot use 0, because (a power of) y is used as a denominator in the division; you'd get division by zero error. Personally, I'd check whether the result ought to be positive or negative, and smaller than or greater than one in magnitude; two rules overall to pick a safe initial y.

Questions?

Nominal Animal
  • 34,734
  • 4
  • 49
  • 79
  • @DavidC.Rankin: I don't know if MathML is supported for Markdown in Stackoverflow, that's why I resorted to Unicode graphics. If there is a better way to represent the formulae, editing this answer would be appreciated! Being a Linux user with a need of simple notetaking in plain text files (C comments, often1), UTF-8 and Unicode glyphs (using Character Map widget for lookup) makes it rather easy. I have wondered whether a dedicated GTK+ widget to make typical diagram drawing even easier would be helpful to others.. – Nominal Animal Jul 04 '14 at 06:31
  • Oh no, I thought it was excellent. Again, well done, no sarcasm :) – David C. Rankin Jul 04 '14 at 06:44
2

You can use the generalized binomial theorem. Substitute y=1 and x=a-1. You would want to truncate the infinite series after enough terms, based on the desired accuracy. To be able to link number of terms to accuracy, you would need to ensure that the x^r terms are decreasing in absolute value. So, depending on the value of a and n, you should apply the formula to compute one of a^n and a^(-n) and use that to get your desired result.

Pradhan
  • 15,095
  • 3
  • 39
  • 56
2

A solution for raising an integer number to a power is:

int poweri (int x, unsigned int y)
{
    int temp;
    if (y == 0)
        return 1;

    temp = poweri (x, y / 2);
    if ((y % 2) == 0)
        return temp * temp;
    else
        return x * temp * temp;
}

However, the square root doesn't provide as clean of a closed solution. There is a good bit of background to be found at wikipedia-square root and at Wolfram Mathworks Square Root Algorithms Both provide several methods that will meet your needs, you just have to choose the one that fits your purpose.

With slight modification, this routine from wikipedia (modified to return the square root and refine accuracy) returns a surprisingly accurate square root. Yes, there will be howls about the use of a union, and it is only valid where integer and float storage are equivalent, but if you are hacking your own square root, this is relatively efficient:

float sqrt_f (float x)
{
        float xhalf = 0.5f*x;
        union
        {
            float x;
            int i;
        } u;
        u.x = x;
        u.i = 0x5f3759df - (u.i >> 1);
        /* The next line can be repeated any number of times to increase accuracy */
        // u.x = u.x * (1.5f - xhalf * u.x * u.x);
        int i = 10;
        while (i--)
            u.x *= 1.5f - xhalf * u.x * u.x;

        return 1.0f / u.x;
}
David C. Rankin
  • 69,681
  • 6
  • 44
  • 72
  • about the inverse sqrt: from personal experience and testing, 3 Newtons iterations are enough to get to machine accuracy with float/single precision. The double precision version instead requires 4. 10 iterations is simply (useless) overkill. – Federico Feb 01 '16 at 09:00