3

I need to create a program that can calculate approximation to the constant PI, using Java multi-thread.

I'm intent to use Gregory-Leibniz Series to calculate the result for PI / 4, and then multiply by 4 to get the PI approximation.

enter image description here

But I have some concern about the program:

  1. How can I seperate the calculation process so that I can implement a multi-thread processing for the program? Because the formula is for the total sum, I don't know how to split them into parts and then in the end I will collect them all.
  2. I'm thinking about the fact that the program will execute the formula to infinite so user will need to provide some means of configuring the execution in order to determine when it should stop and return a result. Is it possible and how can I do that?

This is so far the most I can do by now.

public class PICalculate {

    public static void main(String[] args) {
        System.out.println(calculatePI(5000000) * 4);
    }

    static double calculatePI(int n) {
        double result = 0.0;
        if (n < 0) {
            return 0.0;
        }
        for (int i = 0; i <= n; i++) {
            result += Math.pow(-1, i) / ((2 * i) + 1);
        }
        return result;
    }
}
Thong Vo
  • 809
  • 1
  • 9
  • 21
  • 3
    that series converges very slowly.... – Mitch Wheat Dec 12 '16 at 04:06
  • I guess you dont understand threads very well so go back and read the textbooks and practice some examples – gpasch Dec 12 '16 at 04:12
  • One problem with a multithreaded approach is that in order to get a good result, you need to add the small terms first, due to floating point rounding. Note also that this series is very slow to converge. – Bathsheba Dec 12 '16 at 07:13
  • @Bathsheba What do you mean 'add the small terms first'? – Thong Vo Dec 12 '16 at 07:49
  • 1
    It's a quirk of floating point. Adding a very small number to a very large number will evaluate simply to the larger number. So you need to be careful when summing terms in a series: you need to start with the small terms first. – Bathsheba Dec 12 '16 at 07:54

1 Answers1

1

The most straightforward, but not the most optimal, approach is to distribute the sequence elements between threads you have. Ie, if you have 4 threads, thread one will work with n%4 == 0 elements, thread2 with n%4 == 1 elements and so on

public static void main(String ... args) throws InterruptedException {

    int threadCount = 4;
    int N = 100_000;
    PiThread[] threads = new PiThread[threadCount];
    for (int i = 0; i < threadCount; i++) {
        threads[i] = new PiThread(threadCount, i, N);
        threads[i].start();
    }
    for (int i = 0; i < threadCount; i++) {
        threads[i].join();
    }
    double pi = 0;
    for (int i = 0; i < threadCount; i++) {
        pi += threads[i].getSum();
    }
    System.out.print("PI/4 = " + pi);

}

static class PiThread extends Thread {

    private final int threadCount;
    private final int threadRemainder;
    private final int N;
    private double sum  = 0;

    public PiThread(int threadCount, int threadRemainder, int n) {
        this.threadCount = threadCount;
        this.threadRemainder = threadRemainder;
        N = n;
    }


    @Override
    public void run() {
        for (int i = 0; i <= N; i++) {
            if (i % threadCount == threadRemainder) {
                sum += Math.pow(-1, i) / (2 * i + 1);
            }
        }
    }

    public double getSum() {
        return sum;
    }
}

PiThread is more efficient, but arguably harder to read, if the loop is shorter:

public void run() {
    for (int i = threadRemainder; i <= N; i += threadCount) {
        sum += Math.pow(-1, i) / (2 * i + 1);
    }
}

In case you don't want to limit yourself with number of elements in sequence and just by time, you may follow an approach below. But note, that it is still limited with Long.MAX_VALUE and you'll have to use BigIntegers, BigDecimals or any other reasonable approach to improve it

public static volatile boolean running = true;

public static void main(String ... args) throws InterruptedException {
    int threadCount = 4;
    long timeoutMs = 5_000;
    final AtomicLong counter = new AtomicLong(0);
    PiThread[] threads = new PiThread[threadCount];
    for (int i = 0; i < threadCount; i++) {
        threads[i] = new PiThread(counter);
        threads[i].start();
    }

    Thread.sleep(timeoutMs);
    running = false;

    for (int i = 0; i < threadCount; i++) {
        threads[i].join();
    }

    double sum = 0;
    for (int i = 0; i < threadCount; i++) {
        sum += threads[i].getSum();
    }
    System.out.print("counter = " + counter.get());
    System.out.print("PI = " + 4*sum);

}

static class PiThread extends Thread {

    private AtomicLong counter;
    private double sum  = 0;

    public PiThread(AtomicLong counter) {
        this.counter = counter;
    }


    @Override
    public void run() {
        long i;
        while (running && isValidCounter(i = counter.getAndAdd(1))) {
            sum += Math.pow(-1, i) / (2 * i + 1);
        }
    }

    private boolean isValidCounter(long value) {
        return value >= 0 && value < Long.MAX_VALUE;
    }

    public double getSum() {
        return sum;
    }
}
Anton Dovzhenko
  • 2,399
  • 11
  • 16
  • What is the limited with Long.MAX_VALUE? Also, how does the second approach work? Each thread will wait for amount of time for another to die and then continue? I still not know how does each thread keep their own result and another thread don't step on previous calculating? – Thong Vo Dec 12 '16 at 09:17
  • I've noticed that in the second approach, the program will not exit after finish calculating. – Thong Vo Dec 12 '16 at 09:26
  • Long.MAX_VALUE limits the number of elements in the sequence. All the threads are running simultaneously, keeping partial sum in corresponding variable. After complementary threads execution is finished or interrupted by timeout, all the partial sums are added together. – Anton Dovzhenko Dec 12 '16 at 10:29
  • Yow are right, it would be correct to interrupt threads in a different way, as join doesn't kill them. You may add isInterrupted check to while loop and interrupt them after the join. – Anton Dovzhenko Dec 12 '16 at 10:31
  • - "All the threads are running simultaneously, keeping partial sum in corresponding variable." I'm still not understand this part, because you initialize 4 thread that mean each thread will be construct with initial value, meaning the sum will be reset to 0 and the counter too. How does all threads sharing the sum toghether? – Thong Vo Dec 13 '16 at 02:18
  • - "You may add isInterrupted check to while loop and interrupt them after the join." I've added `thread[i].interrupt()` after the `join()` statement but it's not working. – Thong Vo Dec 13 '16 at 02:19
  • Oh, I got this. After adding `thread[i].interrupt()`, in the while loop I will check if current thread is interrupted or not, if yes, break the loop. `while (isValidCounter(i = counter.getAndAdd(1)) && !isInterrupted()) { sum += Math.pow(-1, i) / (2 * i + 1); }` – Thong Vo Dec 13 '16 at 03:20
  • I updated the code to fix interruption issue. You may replace Thread.sleep(timeoutMs) with waiting the input from keyboard etc if you want to interrupt calculation manually. – Anton Dovzhenko Dec 13 '16 at 13:24
  • If I want to waiting the input from keyboard to interrupt calculation manually, I should create another thread wait for a particular key or any key to be pressed to interrupt the calculation threads? – Thong Vo Dec 13 '16 at 15:33
  • 1
    No new thread required. Keyboard interception is done in the main Thread. As an example you may replace `Thread.sleep(5_000)` with `Scanner keyboard = new Scanner(System.in); System.out.println("Press Enter to stop calculation"); keyboard.next();` – Anton Dovzhenko Dec 14 '16 at 02:24
  • Really appreciate for your help. I will convert to ExecutorService instead of creating new Thread for more efficient. – Thong Vo Dec 15 '16 at 03:19
  • Sorry for disturb you again, but I have some concern: Why does in the loop for counting partial sum, I change from `sum += Math.pow(-1, i) / (2 * i + 1)` to an if/else to check for the `i`. If `i % 2 == 0` => `sum += 1 / (2 * i + 1)`, else `sum -= 1 / (2 * i + 1)`. But the result is incorrect, I always get pi = 4 because all the partial sums is all 0, only one is 1. – Thong Vo Jan 06 '17 at 07:41
  • The idea is I want to get rid of the `Math.pow()` for better performance. – Thong Vo Jan 06 '17 at 07:42
  • `Math.pow()` returns double and in this case `1.0/10 = 0.1`. But it you replace it with 1, you will get `int` divided by `int`, which returns `int`: `1 / 3` will return `0` etc. Replace `1` with `1.0` or `1d` to get correct output – Anton Dovzhenko Jan 06 '17 at 07:54
  • Also if you concerned about Math.pow takes much time, that's not an issue. I would assume that there will be no performance difference between `Math.pow(-1, 1)` and `Math.pow(-1,1000000)`. Modern JVMs and processors are rather smart to optimize it ... somehow. Moreover it may be faster than if-else branch, as modern processors are well optimized for Maths, but not for branching. – Anton Dovzhenko Jan 06 '17 at 07:58
  • Ok I see the problem here. Also, I think the check for `counter >= 0` is a bit redundant, we only need to check if `counter < Long.MAX_VALUE` – Thong Vo Jan 06 '17 at 09:17
  • Right, `counter < Long.MAX_VALUE` check is sufficient – Anton Dovzhenko Jan 06 '17 at 09:25
  • What does `threadRemainder` mean? I could not really understand what does the value of that variable represents? – zodiac645 May 02 '20 at 22:28
  • Not the best name to be fair. This act like a thread number. Thread #0 processes all values where `i % threadCount == 0`, thread #` processes all values where `i % threadCount == 1` and so force – Anton Dovzhenko May 03 '20 at 11:08