使用Java语言计算圆周率

首先

在这篇文章中,我们将使用以下的公式,在Java中计算圆周率。

\frac{4}{\pi}=\sum_{n=0}^\infty \frac{(-1)^n(4n)!(1123+21460n)}{882^{2n+1}(4^nn!)^4}

拉马努金(1887年12月22日至1920年4月26日)的公式。
仔细观察,会让人不禁思考他是如何想出这个公式的,真的能得出圆周率吗?让人感到非常神奇。

(我不知道是关于哪个方程式的对话),以下的对话也充满神秘色彩。

哈迪教授:“能告诉我你是如何想到这个数学公式的吗?”
拉马努贾恩:“请相信,是纳玛吉利女神站在我的枕边教给我的。”

我真的想试着进行计算。

编程

基本上,这是一个没有创意的程序。

    • 分子と分母はそれぞれBigIntegerで計算しています。

 

    • 分数を割り算するところからBigDecimalで計算します(このプログラムでは、ここではじめて精度指定します)。

 

    • 階乗は同じ計算を何度もするのはあまりにひどいので、前の計算結果を再利用するよう「工夫」しています。

 

    最後の方の桁は誤差を含みます(誤差は「繰り返し回数」に比例します)。
/**
 * 円周率を計算します。
 */

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

public class RamaPi {
    /**
     * 階乗を計算します(直前の計算結果を再利用します)。
     */
    class Facto {
        /** 直前の引数 */
        int n = 1;
        /** 直前の値 */
        BigInteger val = BigInteger.ONE;

        BigInteger calc(final int arg) {
            for ( ; n <= arg ; n++) {
                val = val.multiply(BigInteger.valueOf(n));
            }

            return val;
        }
    }

    /** 階乗計算用1 */
    Facto workFacto1 = new Facto();
    /** 階乗計算用2 */
    Facto workFacto2 = new Facto();
    /** 定数「882」 */
    static final BigInteger VAL_882 = BigInteger.valueOf(882);
    /** 定数「4」 */
    static final BigInteger VAL_4 = BigInteger.valueOf(4);

    /**
     * ラマヌジャンの公式の分子を計算します。
     */
    BigInteger bunsi(final int n) {
        final int sign = (n&1) == 0 ? 1 : -1;
        final BigInteger a = workFacto1.calc(4 * n);
        final BigInteger b = BigInteger.valueOf(sign * (1123 + 21460*n));

        return a.multiply(b);
    }

    /**
     * ラマヌジャンの公式の分母を計算します。
     */
    BigInteger bunbo(final int n) {
        final BigInteger a = VAL_882.pow(2*n+1);
        final BigInteger b = VAL_4.pow(n);
        final BigInteger c = workFacto2.calc(n);
        final BigInteger d = b.multiply(c).pow(4);
        return a.multiply(d);
    }

    /**
     * ラマヌジャンの公式から、円周率を求めます。
     */
    void calc(final int digits) {
        BigDecimal sum = BigDecimal.ZERO;

        /** 加算を打ち切る桁数(この桁数未満は足せません) */
        final BigDecimal thr = BigDecimal.ONE.divide(BigDecimal.TEN.pow(digits));

        /** 計算開始した時間 */
        long t0 = System.nanoTime();

        int iter = 0;
        for ( ;; iter++ ) {
            final BigDecimal bunbo = new BigDecimal(bunbo(iter));
            final BigDecimal bunsi = new BigDecimal(bunsi(iter));

            final BigDecimal adder = bunsi.divide(bunbo, digits, RoundingMode.HALF_EVEN);

            if (adder.abs().compareTo(thr) < 0) {
                break;
            }

            /* 加算の結果で、sumを書き換えます */
            sum = sum.add(adder);

            /* 桁数が小さい場合、収束の様子を表示します */
            if (digits < 100) {
                System.out.println( BigDecimal.valueOf(4).divide(sum, digits, RoundingMode.HALF_EVEN));
            }
        }

        /** 計算終了した時間 */
        long t1 = System.nanoTime();
        System.err.println("繰り返した回数: " + iter);
        double elapsed = (t1 - t0) / ((double)1000 * 1000 * 1000);
        System.err.println("所要した秒数: " + String.format("%.6f", elapsed));

        System.out.println(BigDecimal.valueOf(4).divide(sum, digits, RoundingMode.HALF_EVEN));
    }

    static public void main(String arg[]) {
        int digits = Integer.parseInt(arg[0]);

        new RamaPi().calc(digits);
    }
}

执行示例

在参数中,指定位数。
可能会在最后的几位上存在误差(误差与“重复次数”成比例)。

java RamaPi 50
3.14158504007123775601068566340160284951024042742654
3.14159265359762201426827517920229156338712376594028
3.14159265358979322988767708881201158250039662639857
3.14159265358979323846265313702144396820307036308333
3.14159265358979323846264338326814215966511742779251
3.14159265358979323846264338327950289764449222972749
3.14159265358979323846264338327950288419715329619277
3.14159265358979323846264338327950288419716939939455
3.14159265358979323846264338327950288419716939937511
繰り返した回数: 9
所要した秒数: 0.003002
3.14159265358979323846264338327950288419716939937511

需要多长时间

在我所处的环境中进行了一些测试。虽然花费了相当多的时间,但是我们是将Java的多倍长计算视为快速还是慢速呢?

桁数繰り返した回数所要した秒数1回あたりμs1020.001129564.5100180.003077170.910001700.049033288.41000016984.2371592495.320000339519.830755841.1400006790118.65170617474.4

此外,在相同的环境下尝试运行「Superπ」,结果是「838万位数」用时「152秒」(位数相差200倍)。

如果追求更快的速度,与其使用 Java 的 BigInteger / BigDecimal 的多倍长运算,倒不如明显地使用 GMP(GNU 多精度算术库)或其他类似工具更好。

其他

起初,我试图实现一个简单的阶乘计算,但在只有2万位的情况下,竟然需要耗费113秒的时间。(一开始只是随便尝试使用递归法编写,结果很快就遇到了栈溢出的问题。)

    /**
     * 階乗を計算します。
     */
    static final BigInteger facto(final int a) {
        BigInteger work = BigInteger.ONE;

        for (int i=2 ; i<=a ; i++) {
            work = work.multiply(BigInteger.valueOf(i));
        }
        return work;
    }

请提供更多的上下文或者具体的句子,以便我能够为您提供更准确的汉语翻译。

    • シュリニヴァーサ・ラマヌジャン(Wikipedia)

 

    • 任意精度演算 についての紹介記事(Wikipedia)

 

    • よりちゃんとした多倍長演算を行いたいときは GMP

 

    著名な円周率計算プログラム
广告
将在 10 秒后关闭
bannerAds