円周率を Machin’s formula で求める

一年近く前に円周率をモンテカルロ法を利用して求めるプログラムを組んだ。

今回は マチンの公式 ( Machin’s formula ) を利用してみようと思う。

そして桁数と精度も上げてみたいと思ってこの方法を選択してみた。

まず、マチンの公式とは次のようになっている。

ifrm0001

この公式の証明は三角関数の公式を利用して証明できます。

倍角公式を二度使い、加法定理でとどめを刺して終わりです。

詳しくはググってみてください。

それではプログラムをこれにそって組んでみましょう。

プログラムを組むにあたって都合の良いように式を変えてあります。( 6 行目 )

このプログラムでは arcTan(double x) の n 番目 (i + i + 1) の項が 0 になるまで計算をします。

十分小さな値をとるまでに変更する場合は、if 文の条件は絶対値をとるように変更すればいいでしょう。

package jp.yucchi.highprecisionpi;

public class HighPrecisionPi {

    public static void main(String[] args) {
        double pi = 16.0 * arcTan(1.0 / 5.0) - 4.0 * arcTan(1.0 / 239.0);
        System.out.println(pi);
    }

    private static double arcTan(double x) {
        double ret = 0.0;
        double sig = x;
        double sqx = x * x;
        for (int i = 0; sig != 0.0; i++) {
            ret += sig / (double) (i + i + 1);
            sig = -sig * sqx;
        }
        return ret;
    }
}

さて、このプログラムの実行結果を確認してみましょう。

3.141592653589794

最後の桁が 3 であるところが 4 になってます。

モンテカルロ法より速くて精度は高いですね。

でも、もっと桁数をとりたくないでしょうか?

そこで、このプログラムを次のように変更してみました。

百万桁の計算をさせるために BigDecimal を使ってみました。

基本的に先ほどのプログラムと同じです。

ちょっと処理時間が気になるので時間計測をいれてます。

と言っても百万桁も計算させると凄く時間がかかってしまうのは解りきっていることなので一万桁で試してみます。

普段使う機会が少ない BigDecimal を使ってますのでちょっとおかしいところがあるかもしれませんが気にしないでね。(^_^;)

あと、桁数を多くとるので 0 収束を使うと非常に時間がかかってしまうので誤差がでないように適当にループさせてます。

それではいつものように未熟で汚いコードですがご覧ください。

package jp.yucchi.highprecisionpi;

import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HighPrecisionPi implements Callable {

    private static final int KETA = 10_000;
    private static final int LIMIT_NUMBER = KETA + 2;
    private static final int LIMIT_NUMBER2 = KETA ;
    private static long startTime;
    private static long time;
    private final BigDecimal s_q;
    private final BigDecimal _limit;
    private BigDecimal ss;

    public HighPrecisionPi(BigDecimal _limit, BigDecimal ss, BigDecimal s_q) {
        this._limit = _limit;
        this.ss = ss;
        this.s_q = s_q;
    }

    public static void main(String[] args) {
        System.out.println("<--- 円周率計算開始 --->");
        startTime = System.nanoTime();
        //精度KETA桁 四捨五入で丸める
        int setPrecision = KETA;
        RoundingMode setRoundingMode = RoundingMode.HALF_UP;
        MathContext mc2 = new MathContext(1, setRoundingMode);
        MathContext mc = new MathContext(setPrecision, setRoundingMode);
        BigDecimal f = BigDecimal.ZERO;  // 0
        BigDecimal r = BigDecimal.ZERO;  // 0
        BigDecimal one = BigDecimal.ONE;  // 1     
        BigDecimal two = new BigDecimal(2, mc); //2      
        BigDecimal n239 = new BigDecimal(239, mc); // 239
        BigDecimal sq239 = new BigDecimal(57121, mc); // 239^2

        int procs = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
        if (procs > 1) {
            procs = 2;
        }
        ExecutorService executor = Executors.newFixedThreadPool(procs);

        List> futures = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            final BigDecimal _limit;
            final BigDecimal ss;
            final BigDecimal s_q;
            if (i > 0) {
                _limit = new BigDecimal(LIMIT_NUMBER, mc);
                ss = new BigDecimal(0.2, mc2); // 1 / 5
                s_q = new BigDecimal(0.04, mc2); // 1 / 25
            } else {
                _limit = new BigDecimal(LIMIT_NUMBER2, mc);
                ss = one.divide(n239, mc); // 1 / 239        
                s_q = one.divide(sq239, mc); // 1 / 57121     
            }
            futures.add(executor.submit(new HighPrecisionPi(_limit, ss, s_q)));
        }

        try {
            f = futures.get(1).get().multiply(new BigDecimal(16, mc));
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(HighPrecisionPi.class.getName()).log(Level.SEVERE, null, ex);
        }

        try {
            r = futures.get(0).get().multiply(new BigDecimal(4, mc));
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(HighPrecisionPi.class.getName()).log(Level.SEVERE, null, ex);
        }

        System.out.println("\n" + KETA + "桁の円周率は、\n" + f.subtract(r, mc));
        time = System.nanoTime() - startTime;
        System.out.println("\n" + KETA + "桁の円周率計算時間は、"
                + (int) (time * 1e-9) / 3_600 + "時間"
                + (int) ((time * 1e-9) / 60) % 60 + "分"
                + (int) (time * 1e-9 % 60) + "秒"
                + Double.toString((time * 1e-9 % 60) % 1).substring(2) + "\n");

        System.out.println("<--- 円周率計算終了 --->");

        executor.shutdown();
    }

    @Override
    public BigDecimal call() throws Exception {
        int setPrecision = KETA;
        RoundingMode setRoundingMode = RoundingMode.HALF_UP;
        MathContext mc = new MathContext(setPrecision, setRoundingMode);
        MathContext mc2 = new MathContext(1, setRoundingMode);
        BigDecimal fr = BigDecimal.ZERO;  // 0
        BigDecimal p2 = BigDecimal.ZERO;  // 0
        BigDecimal one = BigDecimal.ONE;  // 1     
        BigDecimal two = new BigDecimal(2, mc); //2 

        for (int i = 0; p2.compareTo(_limit) < 0; i++) {
            BigDecimal bi = new BigDecimal(i, mc);
            fr = fr.add(ss.divide((bi.multiply(two).add(one)), mc));
            ss = ss.multiply(s_q, mc).negate();
            p2 = p2.add(one);
        }
        return fr;
    }
}

少しでも計算時間が短くなればいいなぁって思って悪あがきしてます。( ̄。 ̄;)

基本的な考え方は double 仕様のものと一緒で悪あがきの並行処理を取り入れただけです。

実行結果の抜粋を載せておきます。

<--- 円周率計算開始 --->

10000桁の円周率は、
3.1415926535897932384626433832795028841971693993751058209749445923078164062

略

4988727584610126483699989225695968815920560010165525637568

10000桁の円周率計算時間は、0時間3分22秒4562651430000244

<--- 円周率計算終了 --->

ちゃんと計算できてますね。

一番最後は丸めによって四捨五入されてます。

もっと効率よくプログラムを組むには収束値を設定したほうがいいのかなぁ・・・?

でも、速くても計算が甘くなるのはいやだ!

とりあえず昨年作ったモンテカルロ法で円周率を求めるプログラムより遙かに良いのは確かだ!

あと気のせいかもしれないが BigDecimal の除算って遅く感じる。

ゴールデンウィークの最後の休日にこんなことしているってのは平和でいいね。(^_^)

平成25年5月8日(水) 追記 計算のループの回数を決めている LIMIT_NUMBER の値を変更しました。

10000 桁で確認したところ問題なさそうでした。

ただし、それより大きな桁は確認していませんのであしからず。(;´Д`)

Hatena タグ:

もっと Lambda その7

今日も Java8 がらみのネタです。

今回はちょっといつもと違って私が愛用している高機能な開発環境について少しだけ触れてみたいと思います。

日本でも人気赤丸急上昇中の NetBeans です。

今は Java8 を試すのに開発版を使っています。

注目の Lambda にも徐々にですが対応してます。

以前にも少し紹介したので今回は逆パターンを紹介します。

つまり、Lambda を使わない無名インナークラスを使用する標準的な(?)コードに変換するという試みです。

サンプルは前回のエントリーの JavaCury.java です。

package jp.yucchi.javacurry;

import java.util.function.Function;
import java.util.function.BiFunction;

public class JavaCurry {

    public static void main(String[] args) {

        Function> house = java -> curry -> java + curry;
        System.out.println(house.apply("ジャワ").apply("カレー"));

        BiFunction h = (java, curry) -> java + curry;
        System.out.println(h.apply("ジャワ", "カレー"));
        
        Function>> house2 = java -> curry -> level -> java + curry + level;
        System.out.println(house2.apply("ジャワ").apply("カレー").apply("甘口"));
        
        Function> h2 = java -> (curry, level) -> java +curry + level;
        System.out.println(h2.apply("ジャワ").apply("カレー","辛口"));
    }
}

では、実際にどのように変換されるのかスクリーンショットを撮ってみました。

10 行目の Lambda 式を変換してみます。

2

3

4

5

6

7

8

9

見事に変換されました。さすが NetBeans !

では、もう少し試してみましょう。

10

この Lambda 式は引数の型が省略されてます。

ちまたでは Lambda 式の型省略は企業によっては禁止されるんじゃないかと噂されてます。

それでか、どうかは解りませんが省略された型をコードに追加する機能もあります。

さっそく試してみましょう。

11

12

省略された型がちゃんと追加され表示されてますね。(^_^)

ついでに Lambda 式を複数行で書く場合を試してみます。

13

14

ちゃんと return 文もつくられてますね。

さすがです!

では、無名クラスを使うコードに変換してます。

15

16

17

18

全ての Lambda 式を無名クラスを使うコードに変換したプログラムはこのようになりました。

package jp.yucchi.javacurry;

import java.util.function.Function;
import java.util.function.BiFunction;

public class JavaCurry {

    public static void main(String[] args) {

        Function> house = new Function>() {

            @Override
            public Function apply(String java) {
                return new Function() {

                    @Override
                    public String apply(String curry) {
                        return java + curry;
                    }
                };
            }
        };
        System.out.println(house.apply("ジャワ").apply("カレー"));

        BiFunction h = new BiFunction() {
            @Override
            public String apply(String java, String curry) {
                return java + curry;
            }
        };
        System.out.println(h.apply("ジャワ", "カレー"));

        Function>> house2 = new Function>>() {

            @Override
            public Function> apply(String java) {
                return new Function>() {

                    @Override
                    public Function apply(String curry) {
                        return new Function() {

                            @Override
                            public String apply(String level) {
                                return java + curry + level;
                            }
                        };
                    }
                };
            }
        };
        System.out.println(house2.apply("ジャワ").apply("カレー").apply("甘口"));

        Function> h2 = new Function>() {

            @Override
            public BiFunction apply(String java) {
                return new BiFunction() {

                    @Override
                    public String apply(String curry, String level) {
                        return java + curry + level;
                    }
                };
            }
        };
        System.out.println(h2.apply("ジャワ").apply("カレー", "辛口"));
    }
}

これをみると Lambda 式ってけっこうイケてるかなって思います。

ちなみに Java8 と Java8 対応の NetBeans はまだ開発中なので動作が完全ではありません。

でも、開発版でグリグリやってると開発チームの熱い思いはしっかりと伝わってきます。

きっと Java8 対応の NetBeans もいつも通り素晴らしい出来でリリースされると確信してます。(^_^)

Technorati タグ: ,