使用BigDecimal打印Pi

时间:2018-02-24 00:30:30

标签: java bigdecimal pi

我想使用BigDecimal打印Pi,但事实证明它不准确

    BigDecimal d = new BigDecimal(Math.PI); 
    System.out.println(d); 

以上回答给了我" 3.141592653589793115997963468544185161590576171875" 但" 3.141592653589793"之后的数字;是不正确的

这是怎么回事?我可以用bigdecimal打印PI吗?

1 个答案:

答案 0 :(得分:1)

是的,您可以使用BigDecimal来打印π,但令人失望的是,最简单,最有效的方法可能是从Wolfram Mathematica这样的专用计算引擎复制数字并将其粘贴到Java中。 String,然后使用它来初始化BigDecimal

Zabuzard在评论中建议进行实际计算π并将其放入BigDecimal的过程。

在这里重要的是要了解π是无理数和先验数。这意味着无论我们将任何位或数字序列放在一起,都始终是有理近似。

Math.PI中的近似值适合64位或16个十进制数字。为了使近似值值得投入BigDecimal,它必须大于64位。也许是2,048位?

然后您需要一个好的公式,以在合理的时间内给出所需的精度。我选择了莱布尼兹公式。大错。我的初稿返回1.0,以确保它不会通过第一次测试。但是我可以很容易地从莱布尼兹公式开始,但仍未通过第一次测试。

package org.oeis.pi;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class BigDecimalPi {
    private static final BigDecimal PI_QUARTERS;

    private static final MathContext PREC_2048 
            = new MathContext(2048, RoundingMode.HALF_EVEN);

    static {
        BigDecimal number = BigDecimal.ZERO;
        final BigDecimal two = BigDecimal.ONE.add(BigDecimal.ONE);
        final BigDecimal negOne = BigDecimal.ONE.subtract(two);
        BigDecimal powerOfNegOne = BigDecimal.ONE;
        final BigDecimal precLim = new BigDecimal("10000000");
        BigDecimal reciprocal;
        for (BigDecimal i = BigDecimal.ONE; i.compareTo(precLim) < 0; i = i.add(two)) {
            reciprocal = powerOfNegOne.divide(i, 1000, RoundingMode.CEILING);
            number = number.add(reciprocal);
            powerOfNegOne = powerOfNegOne.multiply(negOne);
        }
        PI_QUARTERS = number;
    }

    private static final BigDecimal FOUR = new BigDecimal("4");

    public static BigDecimal pi() {
        return PI_QUARTERS.multiply(FOUR);
    }
}

要对此进行测试,请从您信任的来源获取数字。我使用Wolfram Mathematica,然后将输出粘贴到我的JUnit测试中。

package org.oeis.pi;

import java.math.BigDecimal;
import java.math.MathContext;

import org.junit.Test;

public class BigDecimalPiTest {

    private static final String THOUSAND_PI_DIGITS = "3." 
            + "1415926535897932384626433832795028841971693993751" // 1 to 49
            + "05820974944592307816406286208998628034825342117067" // 50 to 99
            + "98214808651328230664709384460955058223172535940812" // 100 to 149
            + "84811174502841027019385211055596446229489549303819" // 150 to 199
            + "64428810975665933446128475648233786783165271201909" // 200 to 249
            + "14564856692346034861045432664821339360726024914127" // 250 to 299
            + "37245870066063155881748815209209628292540917153643" // 300 to 349
            + "67892590360011330530548820466521384146951941511609" // 350 to 399
            + "43305727036575959195309218611738193261179310511854" // 400 to 449
            + "80744623799627495673518857527248912279381830119491" // 450 to 499
            + "29833673362440656643086021394946395224737190702179" // 500 to 549
            + "86094370277053921717629317675238467481846766940513" // 550 to 599
            + "20005681271452635608277857713427577896091736371787" // 600 to 649
            + "21468440901224953430146549585371050792279689258923" // 650 to 699
            + "54201995611212902196086403441815981362977477130996" // 700 to 749
            + "05187072113499999983729780499510597317328160963185" // 750 to 799
            + "95024459455346908302642522308253344685035261931188" // 800 to 849
            + "17101000313783875288658753320838142061717766914730" // 850 to 899
            + "35982534904287554687311595628638823537875937519577" // 900 to 949
            + "81857780532171226806613001927876611195909216420198" // 950 to 999
            ;

然后我让测试班从粘贴的数字中制作一个BigDecimal

    private static final MathContext PRECISION_2048_BITS = new MathContext(2048);

    private static final BigDecimal PI_FROM_THOUSAND_DIGITS 
            = new BigDecimal(THOUSAND_PI_DIGITS, PRECISION_2048_BITS);

然后是关键时刻。我写的测试容限为100个十进制数字。

    @Test
    public void testPi() {
        System.out.println("pi");
        System.out.println("Expecting  " + PI_FROM_THOUSAND_DIGITS.toEngineeringString());
        BigDecimal actual = BigDecimalPi.pi();
        System.out.println("Got        " + actual.toEngineeringString());
        BigDecimal tolerance = new BigDecimal("1E-100");
        BigDecimal delta = PI_FROM_THOUSAND_DIGITS.subtract(actual).abs();
        System.out.println("Difference " + delta.toEngineeringString());
        String assertionMessage = "Difference should be within tolerance";
        assert delta.compareTo(tolerance) < 1 : assertionMessage;
    }

}

对于仅返回1.0的初稿,测试失败了2.14159 ...,符合预期。但是,对于我第一次使用莱布尼兹公式的测试,测试几乎失败了0.01。当我将precLim增加到10000000时,测试失败了199.9999999999980000000000000000000999999999999878 ... E-9,并且花费了将近24秒。

因此,要获得所需的精度,您将必须研究π的公式以找到更快的公式。数学术语是“收敛”。更快的收敛可能会转化为更快的算法。

经过所有这些努力,您可能会决定仅复制和粘贴数字就足够了。

再说一次,我读到某处天文学家只需要π到16位数字。这将意味着Math.PI对于天文计算将是相当不错的。如果Fortran具有π的内置常数,则可能达到该精度。