Вычисление числа пи с использованием BigDecimals и ряда Нилаканта не работает для более чем 10 знаков после запятой.

Вот мой код:

import java.math.BigDecimal;

public class Challenge6 {

    private static final String PI = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295";

    public static BigDecimal calculatePi(int decimals) {

        BigDecimal pi = new BigDecimal(3);
        boolean toggle = true;
        boolean enough = false;
        int i = 2;

        while (!enough) {
            BigDecimal num = new BigDecimal(
                    (double) (4 / (double) (i * (i + 1) * (i + 2))));
            System.out.println(i / 2);

            if (toggle) {
                pi = pi.add(num);
                toggle = false;

            } else {
                pi = pi.subtract(num);
                toggle = true;
            }
            i += 2;

            try {
                if (PI.substring(0, decimals + 2).equals(
                        ("" + pi).substring(0, decimals + 2))) {
                    enough = true;
                } else {

                }
            } catch (Exception ex) {
                continue;
            }
        }
        return pi;
    }

    public static void main(String[] args) {
        System.out.printf("Pi: %.30s", calculatePi(10));
    }
}

Я знаю, что это не идеально, но это просто для удовольствия. Мне просто интересно, почему моя программа может идеально генерировать число пи до 10 знаков после запятой чуть более чем за 700 итераций, но когда я пытаюсь вычислить больше 10, это происходит навсегда (по крайней мере, я предполагаю, что навсегда). Это просто из-за разделения с плавающей запятой или что-то в этом роде? Если да, то почему он просто перестает работать сразу после 10? Кроме того, если вы запускаете программу, выводя число пи на каждой итерации, оно превращается в что-то вроде 3,39. У BigDecimal здесь проблемы, или что-то не так с моим кодом? Любая помощь будет крутой.

P.S. - Мне действительно нужно только 30 знаков после запятой.


person Luke Krauss    schedule 02.12.2015    source источник


Ответы (2)


Когда вы используете выражение «new BigDecimal( (double) (4 / (double) (i * (i + 1) * (i + 2))))», вы используете double, а не BigDecimal. И использование double не может иметь достаточную шкалу для вычисления более 10 знаков после запятой.

Используйте следующий код

public static BigDecimal calculatePi(int decimals) {
    BigDecimal pi = new BigDecimal(3);
    boolean toggle = true;
    boolean enough = false;
    int i = 2;

    while (!enough) {
        BigDecimal a = new BigDecimal(4); // All operation must be using only BigDecimal 
        BigDecimal b = new BigDecimal(i).multiply(new BigDecimal(i + 1)).multiply(new BigDecimal(i + 2));
        BigDecimal num = a.divide(b, 100, RoundingMode.FLOOR);
        System.out.println(i / 2);

        if (toggle) {
            pi = pi.add(num);
            toggle = false;

        } else {
            pi = pi.subtract(num);
            toggle = true;
        }
        i += 2;

        if (PI.substring(0, decimals + 2).equals(("" + pi).substring(0, decimals + 2))) {
            enough = true;
        }
    }
    return pi;
}

P.S. Я проверяю этот код на 20 знаков после запятой, но для 30 знаков после запятой нужно ждать много времени, я думаю

person Slava Vedenin    schedule 02.12.2015
comment
Спасибо! Похоже, моя проблема определенно заключалась в том, что мне нужно было сделать все BigDecimal. Есть идеи, почему мой старый код перестал работать после 10 знаков после запятой? - person Luke Krauss; 03.12.2015
comment
Это не остановка, это работает вечно, но в выражении new BigDecimal( (double) (4 / (double) (i * (i + 1) * (i + 2)))) вы используете double, а не BigDecimal. И использование двойного in't не может иметь достаточный масштаб. - person Slava Vedenin; 03.12.2015
comment
Верно, но он вычисляет число пи до 10 знаков после запятой, но не точно вычисляет число пи после 11. - person Luke Krauss; 03.12.2015
comment
Да, достаточно удвоить до 10 знаков после запятой, но не до 11 знаков после запятой - person Slava Vedenin; 03.12.2015
comment
Это был бы гораздо лучший ответ, если бы ваш первый комментарий был частью ответа. В настоящее время ответ не объясняет, почему он решает проблему. - person VGR; 03.12.2015
comment
Добавить комментарий к ответу - person Slava Vedenin; 03.12.2015

То, как вы используете класс BigDecimal, ничего не делает. Деление как двойное (и точность знаменателя, когда оно становится большим) будет вашими ограничивающими факторами.

Попробуйте выполнить все эти операции, используя компоненты BigDecimal, которые используют операции BigDecimal для деления, и вы, вероятно, также захотите использовать компоненты BigDecimal в знаменателе.

На самом деле вы можете превратить знаменатель в длинное и просто использовать одну операцию деления BigNum, пока длинное не переполнится. Я рекомендую не использовать Double или что-то еще для знаменателя, потому что у вас возникнут проблемы с обнаружением потери точности.

person Bill K    schedule 02.12.2015