3

I'm trying to write a function in Java that calculates the n-th root of a number. I'm using Newton's method for this. However, the user should be able to specify how many digits of precision they want. This is the part with which I'm having trouble, as my answer is often not entirely correct. The relevant code is here: http://pastebin.com/d3rdpLW8. How could I fix this code so that it always gives the answer to at least p digits of precision? (without doing more work than is necessary)

import java.util.Random;

public final class Compute {

    private Compute() {
    }

    public static void main(String[] args) {
        Random rand = new Random(1230);
        for (int i = 0; i < 500000; i++) {
            double k = rand.nextDouble()/100;
            int n = (int)(rand.nextDouble() * 20) + 1;
            int p = (int)(rand.nextDouble() * 10) + 1;
            double math = n == 0 ? 1d : Math.pow(k, 1d / n);
            double compute = Compute.root(n, k, p);
            if(!String.format("%."+p+"f", math).equals(String.format("%."+p+"f", compute))) {
                System.out.println(String.format("%."+p+"f", math));
                System.out.println(String.format("%."+p+"f", compute));
                System.out.println(math + " " + compute + " " + p);
            }
        }
    }

    /**
     * Returns the n-th root of a positive double k, accurate to p decimal
     * digits.
     * 
     * @param n
     *            the degree of the root.
     * @param k
     *            the number to be rooted.
     * @param p
     *            the decimal digit precision.
     * @return the n-th root of k
     */
    public static double root(int n, double k, int p) {     
        double epsilon = pow(0.1, p+2);
        double approx = estimate_root(n, k);
        double approx_prev;

        do {
            approx_prev = approx;
            // f(x) / f'(x) = (x^n - k) / (n * x^(n-1)) = (x - k/x^(n-1)) / n
            approx -= (approx - k / pow(approx, n-1)) / n;
        } while (abs(approx - approx_prev) > epsilon);
        return approx;
    }

    private static double pow(double x, int y) {
        if (y == 0)
            return 1d;
        if (y == 1)
            return x;
        double k = pow(x * x, y >> 1);
        return (y & 1) == 0 ? k : k * x;
    }

    private static double abs(double x) {
        return Double.longBitsToDouble((Double.doubleToLongBits(x) << 1) >>> 1);
    }

    private static double estimate_root(int n, double k) {
        // Extract the exponent from k.
        long exp = (Double.doubleToLongBits(k) & 0x7ff0000000000000L);
        // Format the exponent properly.
        int D = (int) ((exp >> 52) - 1023);
        // Calculate and return 2^(D/n).
        return Double.longBitsToDouble((D / n + 1023L) << 52);
    }
}
skaffman
  • 398,947
  • 96
  • 818
  • 769
gerardlouw
  • 201
  • 3
  • 5

3 Answers3

3

Just iterate until the update is less than say, 0.0001, if you want a precision of 4 decimals.

That is, set your epsilon to Math.pow(10, -n) if you want n digits of precision.

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • That is exactly what I do in my code. I even set my epsilon to pow(0.1, n+2) to improve precision. But when calling root (6, 0.008303709223334907, 1) it returns 0.45000169169604604, where the actual root is 0.4499994905760778... Rounding both to 1 digit of precision gives 0.4 and 0.5 respectively. – gerardlouw Feb 12 '11 at 14:34
  • Your answer appears too precise, but this may have more to do with the root landing right on the edge of rounding. I'm not sure what you can do about that. – Hovercraft Full Of Eels Feb 12 '11 at 14:44
  • As you probably understand, you may need infinite precision to actually tell (for sure) which the first decimal actually is. What it means for "correct decimal places", or "significant figures" is really what you've implemented: [ *The significant figures ... of a number are those digits that carry meaning contributing to its precision.* ](http://en.wikipedia.org/wiki/Significant_figures) – aioobe Feb 12 '11 at 14:49
  • 1
    if you want a precision of 4 decimals, you have to use 0.00005 and not 0.0001. – Axel Feb 12 '11 at 15:13
  • @Axel, wouldn't matter in this particular case. – aioobe Feb 12 '11 at 15:16
  • BTW, why would you choose the newton algorithm? It requires the first derivative of f, which in general is not always easy to determine. In my day job, I have to deal quite a lot with root finding, and we implemented a library for that purpose. I cannot post the source, but we use a combination of several independent algorithms, the most important is a modified secant method. No derivative is needed, and convergence is very good. – Axel Feb 12 '11 at 16:56
2

Let's recall what the error analysis of Newton's method says. Basically, it gives us an error for the nth iteration as a function of the error of the n-1 th iteration.

So, how can we tell if the error is less than k? We can't, unless we know the error at e(0). And if we knew the error at e(0), we would just use that to find the correct answer.

What you can do is say "e(0) <= m". You can then find n such that e(n) <= k for your desired k. However, this requires knowing the maximal value of f'' in your radius, which is (in general) just as hard a problem as finding the x intercept.

What you're checking is if the error changes by less than k, which is a perfectly acceptable way to do it. But it's not checking if the error is less than k. As Axel and others have noted, there are many other root-approximation algorithms, some of which will yield easier error analysis, and if you really want this, you should use one of those.

Xodarap
  • 11,581
  • 11
  • 56
  • 94
0

You have a bug in your code. Your pow() method's last line should read
return (y & 1) == 1 ? k : k * x;
rather than
return (y & 1) == 0 ? k : k * x;

diginoise
  • 7,352
  • 2
  • 31
  • 39