Using BigDecimal to print Pi

痴心易碎 提交于 2020-05-15 09:55:07

问题


I'm tring to use BigDecimal to print Pi, but it turns out it's not accurate

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

The above answer gives me "3.141592653589793115997963468544185161590576171875" but the digits after "3.141592653589793" is incorrect

how this is happenning ? and can i use bigdecimal to print PI ?


回答1:


From Javadoc for Math.PI:

PI

public static final double PI

The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter.

This is roughly (when printed as decimal expansion):

3.141592653589793

It's just a fixed sequence of 64 bits that represents an approximation of Pi, it is a hard-coded constant that is saved in the JAR of the standard library. This 64-bit sequence does not contain an algorithm for computing an arbitrary number of Pi digits.

The numbers that you see after the 15-th place are more or less pseudo-random garbage. Wrapping it into BigDecimal doesn't make it any more precise.




回答2:


This is because the value is precise up to N = 16,

From JAVADoc:

public static final double  PI  3.141592653589793

Since the return type of Math.PI is double and the value is set to the above constant, using the BigDecimal won't make any difference.

Link: https://docs.oracle.com/javase/6/docs/api/constant-values.html#java.lang.Math.PI




回答3:


Yes, you can use BigDecimal to print π, but quite disappointingly, the simplest and most efficient way to do that is probably to copy the digits from a specialized computational engine like Wolfram Mathematica, paste that into a Java String and then use that to initialize a BigDecimal.

In a comment, Zabuzard suggesting going through the process of actually calculating π and putting that into a BigDecimal.

It's important here to understand that π is an irrational and transcendental number. This means that whatever sequence of bits or digits we put together, it's always a rational approximation.

The approximation in Math.PI is good to 64 bits, or 16 decimal digits. For the approximation to be worth putting into BigDecimal, it needs to be good to more than 64 bits. Maybe 2,048 bits?

And then you need a good formula to give the desired precision in a reasonable amount of time. I chose the Leibniz formula. Big mistake. My first draft returned 1.0 so as to make sure it would fail the first test. But I could just as easily have started with the Leibniz formula and still failed the first test.

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);
    }
}

To test this, get the digits from a source you trust. I used Wolfram Mathematica, then pasted that output into my JUnit test.

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
            ;

Then I have the test class make a BigDecimal from the pasted in digits.

    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);

And then the moment of truth. I wrote the test with a tolerance of 100 decimal digits.

    @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;
    }

}

For the first draft that simply returned 1.0, the test failed by 2.14159..., as expected. But for my first go at the Leibniz formula, the test failed by almost 0.01. When I increased precLim to 10000000, the test failed by 199.9999999999980000000000000999999999999878...E-9 and took almost 24 seconds.

So, to get the precision you want, you're going to have to research formulas for π to find a faster formula. The mathematical term is "convergence." Faster convergence might translate to a faster algorithm.

After all that effort, you might decide that just copying and pasting the digits is good enough.

Then again, I read somewhere that astronomers only need π to 16 digits. Which would mean that Math.PI would be quite decent for astronomical calculations. If Fortran has a built-in constant for π, it's probably to that precision.



来源:https://stackoverflow.com/questions/48958077/using-bigdecimal-to-print-pi

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!