Вычисление значения числа пи — что не так с моим кодом

Я делаю еще одно упражнение на С++. Мне нужно вычислить значение числа пи из бесконечного ряда:

pi=4 - 4/3 + 4/5 – 4/7 + 4/9 -4/11+ . . .

Программа должна вывести приблизительное значение числа пи после каждого из первых 1000 членов этого ряда. Вот мой код:

#include <iostream>
using namespace std;

int main()
{
    double pi=0.0;
    int counter=1;

    for (int i=1;;i+=2)//infinite loop, should "break" when pi=3.14159
    {
        double a=4.0;
        double b=0.0;

        b=a/static_cast<double>(i);

        if(counter%2==0)
            pi-=b;
        else
            pi+=b;

        if(i%1000==0)//should print pi value after 1000 terms,but it doesn't  
            cout<<pi<<endl;

        if(pi==3.14159)//this if statement doesn't work as well
            break;

        counter++;
    }

    return 0;
}

Компилируется без ошибок и предупреждений, но после выполнения появляется только пустое консольное окно. Если я удалю строку «if(i%1000==0)», я увижу, что она запускается и печатает каждое значение pi, но не останавливается, что означает, что второй оператор if тоже не работает. Я не знаю, что еще делать. Я предполагаю, что это, вероятно, простая логическая ошибка.


person Mike55    schedule 25.09.2009    source источник
comment
Программирование с плавающей запятой Правило № 2: никогда не сравнивайте нецелые значения с плавающей запятой на равенство.   -  person RBarryYoung    schedule 26.09.2009
comment
@RBarryYoung: Что такое правило №1? :)   -  person Moshe Levi    schedule 27.09.2009


Ответы (10)


Что ж, i % 1000 никогда не будет = 0, так как ваш счетчик начинается с i = 1, а затем с шагом 2. Следовательно, i всегда нечетно и никогда не будет кратно 1000.

Причина, по которой он никогда не завершается, заключается в том, что алгоритм не сходится точно к 3,14157 — это будет более высокая точность как при аппроксимации, так и при избыточной. Вы хотите сказать «Когда в пределах заданной дельты 3,14157», так что пишите

if (fabs(pi - 3.14157) < 0.001)
  break

или что-то подобное, как бы «близко» вы ни хотели подойти, прежде чем остановитесь.

person Adam Wright    schedule 25.09.2009
comment
Я согласен почти со всем вашим ответом. ОДНАКО, вы должны изменить эту строку кода на if (fabs(pi - 3.14157) ‹ 0.001). В противном случае вы проверяете неправильное условие! - person jprete; 26.09.2009
comment
Спасибо за комментарий, jprete - Глупая ошибка, и я изменил пример :) - person Adam Wright; 26.09.2009
comment
Я добавил if (fabs(pi - 3.14157) ‹ 0.001) break, но он все еще не работает. Я включил #‹cmath›, но снова появляется только пустая консоль. - person Mike55; 26.09.2009
comment
Если вы печатаете значение на каждой итерации (т. е. удаляете if), действительно ли оно сходится к такому значению, что |pi - 3,14157| меньше вашей дельты? - person Adam Wright; 26.09.2009
comment
Если я удалю, если он напечатает каждое значение и остановится на 3,14057, что, похоже, работает. Таким образом, если (counter% 1000 == 0) вызывает проблему. - person Mike55; 26.09.2009
comment
Да, и ты решил проблему, что счетчик всегда нечетный? то есть изменить счетчик % 1000 на (счетчик - 1) % 1000? - person Adam Wright; 26.09.2009
comment
Я нашел проблему. Чтобы добраться до 3,14057, требуется 978 терминов, поэтому оно никогда не превысит 1000. Спасибо за помощь! - person Mike55; 26.09.2009
comment
Nitpick: я полагаю, вы имели в виду 3,14159, а не 3,14157. - person David Thornley; 26.09.2009
comment
Я думаю, что неправильно истолковал описание упражнения. Я думаю, что ему просто нужно печатать значение числа пи после каждой 1000 терминов, и ему не нужно останавливаться, когда значение равно 3,14159. Еще раз спасибо за супер быстрые ответы. - person Mike55; 26.09.2009

Поскольку вы начинаете i с 1 и увеличиваете на 2, i всегда является нечетным числом, поэтому i % 1000 никогда не будет равно 0.

person Sterno    schedule 25.09.2009

у вас более одной проблемы:

A. i%1000==0 никогда не будет истинным, потому что вы повторяете только нечетные числа.

B. pi==3.14159 : вы не можете сравнивать двойные значения просто так, потому что представлены числа с плавающей запятой (вы можете прочитать об этом здесь, в другом вопросе). чтобы это работало, вы должны сравнить значения другим способом - один из способов - вычесть их друг из друга и проверить, что абсолютный результат меньше 0,0000001.

person Moshe Levi    schedule 25.09.2009
comment
@Pat: Лучший способ сказать спасибо здесь - проголосовать за ответ. - person innaM; 26.09.2009

  1. У вас проблемы с точностью с плавающей запятой. Попробуйте if(abs(pi - 3.14159) < 0.000005).
  2. i%1000 никогда не будет равно 0, потому что i всегда нечетно.
person chaos    schedule 25.09.2009

Разве не должно быть:

if (counter%1000==0)
person JDunkerley    schedule 25.09.2009
comment
но это заставит его останавливаться каждые 500 шагов, а не каждые 1000 - person JDunkerley; 26.09.2009

  1. i начинается с 1, а затем увеличивается на 2. Поэтому i всегда нечетно и никогда не будет кратно 1000, поэтому if (i % 1000 == 0) никогда не проходит.

  2. Прямое сравнение с плавающей запятой не работает из-за проблем с плавающей точностью. Вам нужно будет сравнить, что разница между значениями достаточно близка.

person Jason Berkan    schedule 25.09.2009

pi=4 - 4/3 + 4/5 – 4/7 + 4/9 -4/11 + ...

Обобщение

pi = i=0 (-1)i 4 / (2< я>я+1)

Что дает нам более чистый подход к каждому термину; i-й термин определяется как:

double term = pow(-1,i%2) * 4 / (2*i+1);

где i=0,1,2,...,N

Таким образом, наш цикл может быть довольно простым, учитывая некоторое количество итераций N

int N=2000;
double pi=0;
for(int i=0; i<N; i++)
{
    double term = pow(-1,i%2) * 4 / (2*(double)i+1);
    pi += term;
    cout << i << "\t" << pi <<endl;
}

Ваш первоначальный вопрос гласил: «Программа должна печатать приблизительное значение числа пи после каждого из первых 1000 членов этой серии». Это не подразумевает никакой необходимости проверять, достигнуто ли 3.14159, поэтому я не включил это здесь. Вызов pow(-1,i%2) предназначен только для того, чтобы избежать операторов if (которые медленны) и предотвратить любые осложнения с большими i.

Имейте в виду, что после ряда итераций разница между величиной пи и величиной корректирующего члена (скажем, -4/25) будет настолько мала, что выйдет за пределы точности double, поэтому вам потребуется более высокая типы точности, чтобы справиться с этим.

person Phil H    schedule 27.09.2009

По умолчанию abs использует макрос abs для int. Для двойников используйте библиотеку cmath.

#include <iostream>
#include <cmath>

int main()
{
    double pi=0.0;

    double a=4.0;
    int i = 1; 

    for (i=1;;i+=2)
    {

        pi += (1 - 2 * ((i/2)%2)) * a/static_cast<double>(i);          

        if( std::abs(pi - 3.14159) < 0.000001 )
              break;

        if (i > 2000) //1k iterations
              break;
    }

    std::cout<<pi<<std::endl;

    return 0;
}
person Todd    schedule 25.09.2009
comment
Я думаю, что оператор if (i›2000) сломается; неверно, потому что он прерывает цикл до достижения 3,14159 (что составляет более 2000 итераций). Добавление переменной счетчика с оператором if if (counter%1000==0) должно это исправить. Выложу исправленный код. - person Mike55; 27.09.2009
comment
Вы правы, что i › 2k ломается до 3.14159 и на самом деле не подходит. В его первоначальной реализации у него было две проверки, ни одна из которых не работала. Так что я оставил его в качестве примера. - person Todd; 28.09.2009

Вот исправленный код. Я подумал, что это может быть полезно в будущем, если у кого-то будет похожая проблема.

#include <iostream>
#include <cmath>

using namespace std;

int main()
{
double pi=0.0;
int counter=1;

for (int i=1;;i+=2)
{
 double a=4.0;
 double b=0.0;

 b=a/static_cast<double>(i);

 if(counter%2==0)
  pi-=b;
 else
  pi+=b;

 if(counter%1000==0) 
  cout<<pi<<" "<<counter<<endl;


 if (fabs(pi - 3.14159) < 0.000001) 
  break;

 counter++;
}
cout<<pi;

 return 0;
}
person Mike55    schedule 27.09.2009

Вот лучше:

class pi_1000
{
public:
    double doLeibniz( int i ) // Leibniz famous formula for pi, source: Calculus II :)
    {
        return ( ( pow( -1, i ) ) * 4 ) / ( ( 2 * i ) + 1 );
    }

 void piCalc()
{
    double pi = 4;
    int i;

    cout << "\npi calculated each iteration from 1 to 1000\n"; //wording was a bit confusing.
                                                    //I wasn't sure which one is the right one: 0-1000 or each 1000th.
    for( i = 1; i < 1000; i++ )
    {
        pi = pi + doLeibniz( i );
        cout << fixed << setprecision( 5 ) << pi << "\t" << i + 1 << "\n";
    }

    pi = 4;
    cout << "\npi calculated each 1000th iteration from 1 to 20000\n";
    for( i = 1; i < 21000; i++ )
    {
        pi = pi + doLeibniz( i );
        if( ( ( i - 1 ) % 1000 )  == 0 )
            cout << fixed << setprecision( 5 ) << pi << "\t" << i - 1 << "\n";
    }

}
person Mr. Vee    schedule 28.09.2013