问题
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