見出し画像

中学生に説明するためのC++予習(5)

5.繰り返し・ループ

 教科書の第5章は繰り返しの構文。for ループとwhileループです。ただし、教科書の内容から離れて勝手なこと書いてみます。

 プログラミング初学者が「ありがたみ」を感じるのって、この繰り返しのあたりからじゃないですかね。
 「そろばん」で、足し算を最初にならったとき、1+2+・・10まで足すとどうなるかやってみよう、55だね、というのをやった。次に1+2+・・100まで5050だね、というのもギリギリやった。疲れたけど。
 公式使うのではなくて、同じことを力技で解く。人力計算では絶対できないスピードで片づけてくれる「武器」としてプログラムできることは魅力では?と思います。(以下は https://www.onlinegdb.com/  を使っています)

数列の和(for文)

#include <iostream>
using namespace std;

int main()
{
    cout << "n  1+2+・・・n" << endl;
    for (int n = 1; n <= 10; n++){ 
        int sum = 0;
        for (int i = 1; i <= n; i++){
            sum = sum + i;
        }
        cout << "n=" << n << "  sum=" << sum << endl;
    }    

    return 0;
}

結果

n  1+2+・・・n
n=1  sum=1
n=2  sum=3
n=3  sum=6
n=4  sum=10
n=5  sum=15
n=6  sum=21
n=7  sum=28
n=8  sum=36
n=9  sum=45
n=10  sum=55

1から10くらいじゃありがたみを感じないか

#include <iostream>
using namespace std;

int main()
{
    cout << "n  1+2+・・・n" << endl;
    for (int n = 10000; n <= 10010; n++){ 
        int sum = 0;
        for (int i = 1; i <= n; i++){
            sum = sum + i;
        }
        cout << "n=" << n << "  sum=" << sum << endl;
    }    

    return 0;
}

結果

n  1+2+・・・n
n=10000  sum=50005000
n=10001  sum=50015001
n=10002  sum=50025003
n=10003  sum=50035006
n=10004  sum=50045010
n=10005  sum=50055015
n=10006  sum=50065021
n=10007  sum=50075028
n=10008  sum=50085036
n=10009  sum=50095045
n=10010  sum=50105055

もう一桁!

#include <iostream>
using namespace std;

int main()
{
    cout << "n  1+2+・・・n" << endl;
    for (int n = 100000; n <= 100010; n++){ 
        int sum = 0;
        for (int i = 1; i <= n; i++){
            sum = sum + i;
        }
        cout << "n=" << n << "  sum=" << sum << endl;
    }    

    return 0;
}

結果

n  1+2+・・・n
n=100000  sum=705082704
n=100001  sum=705182705
n=100002  sum=705282707
n=100003  sum=705382710
n=100004  sum=705482714
n=100005  sum=705582719
n=100006  sum=705682725
n=100007  sum=705782732
n=100008  sum=705882740
n=100009  sum=705982749
n=100010  sum=706082759

あれ、結果がおかしいね? 公式使えば、n=100000 ならsumは5000050000になるはず、というところまできて、前にやった「整数型」を確認してみます。範囲が-2147483648から2147483647 でしたから5000050000は表現できないのね。
 本来、2147483648となるところでー2147483648になってそのあと加算されていくから5000050000-2*2147483648 = 705082704となって、表示がこの値になっているのね。

sum の型をlong longにしましょう。

#include <iostream>
using namespace std;

int main()
{
    cout << "n  1+2+・・・n" << endl;
    for (int n = 100000; n <= 100010; n++){ 
        long long sum = 0;
        for (int i = 1; i <= n; i++){
            sum = sum + i;
        }
        cout << "n=" << n << "  sum=" << sum << endl;
    }    

    return 0;
}

結果

n  1+2+・・・n
n=100000  sum=5000050000
n=100001  sum=5000150001
n=100002  sum=5000250003
n=100003  sum=5000350006
n=100004  sum=5000450010
n=100005  sum=5000550015
n=100006  sum=5000650021
n=100007  sum=5000750028
n=100008  sum=5000850036
n=100009  sum=5000950045
n=100010  sum=5001050055

今度は大丈夫でした。すごい回数の計算しているようだけど一瞬で出るところが中学生くんに感動して欲しいポイント。感動の押し売り(^_^;)

数列の和(while文)

1+2+・・nというのをfor文で

        long long sum = 0;
        for (int i = 1; i <= n; i++){
            sum = sum + i;
        }

と求めました。while文だったらこの部分を

        long long sum = 0;
        int i = 1;
        while (i <= n){
            sum = sum + i;
            i++;
        }

で同じ結果。ついでにdo while文だと、

        long long sum = 0;
        int i = 1;    
        do {
            sum = sum + i;
            i++;            
        } while(i <= n);

これで実行しても、結果が同じになっちゃった。while文とdo while文の違いを説明しようと思ったのにな・・。

whileループとdo whileループの違い

do while のありがたみが分かるような例を下さい、とCopilot先生に頼んでみました。

なるほどねぇ。でもここまで来てから、なんですけど、do while文は一周目では教えなくていいんじゃないか?

円周率

 むかーしむかし、モンテカルロ法で円周率求める問題をやった記憶があるんですけど、今の処理環境は、速度がこんなに速いんだから乱数なんか使わずにしらみつぶしでやったらむしろいい結果出るんじゃないか?とか思いました。
 XY座標で、原点を中心にした、半径10000の円の1/4で・・

#include <iostream>
using namespace std;

int main()
{
    int n = 10000;
    cout << "n: " << n << endl;
    int countin = 0;
    for (int y=0; y <= n; y++){
        for (int x=0; x <= n; x++){
            if ((x*x + y*y)<=n*n){
                countin++;
            }
        }
    }
    cout << "countin: " << countin << endl;
    cout << "pi: " << (4.0 * countin) / n / n;

    return 0;
}

結果

n: 10000
countin: 78549764
pi: 3.14199

なかなかいい線行っているんじゃないかな。

円周率2

教科書の演習問題を見ていたら、同じく円周率を求める問題もありました。
ただし、この式を使うんだそうで、これを1000項まで繰り返せとのこと。
pi = 4 - 4/3 + 4/5 -4/7 +4/9 - 4/11 + 4/13  ・・・
=4(1-1/3 +1/5 -1/7+1/9-1/11+1/13・・・)
i項目で、カッコの中分母は2i-1、i偶数で負、奇数で正だから

#include <iostream>
using namespace std;

int main()
{
    double sum = 0, temp;
    int i=1;
    while(i <= 1000){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << "pi: " << (sum * 4) << endl;

    return 0;
}

結果

pi: 3.14059

まあまあか。でも、精度long double にして1000項までとケチなこと言わずに1000000項まで行っちゃいましょ。

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    long double sum = 0, temp;
    int i=1;
    while(i <= 1000000){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << fixed << setprecision(15) << "pi: " << (sum * 4) << endl;

    return 0;
}

結果

pi: 3.141591653589793

おや?3.14159265358979323・・となるはずなので3.14159[1]6535
というのは小数点以下6桁目が1足りない。おかしいですね。そういえばさっきの1000桁までのときも
3.14059・・と小数点以下3桁目が1足りなかった。ミスってますか?

Copilot先生に聞いてみましょう。

そうなのかなぁ・・
 下の方の桁がずっと合っていることから見て、double のときに小数点以下3桁目、long double の時に小数点以下6桁目がずれている(その後の方は合ってる、というのは何かアルゴリズムをミスってっているからではないかと思うのですが違いますかね。
 まあ、中学生くんにはこの辺黙っておくか(^_^;)。

追記)

 上の、円周率の最後のところ誤魔化したのでした、がよくよく考えてみれば、1000項目と1001項目、およびの高精度の方でも、
1000000項目と1000001項目を比較してみりゃ、誤差なのかバグなのかわかりますよね。

既出)

#include <iostream>
using namespace std;

int main()
{
    double sum = 0, temp;
    int i=1;
    while(i <= 1000){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << "pi: " << (sum * 4) << endl;

    return 0;
}

結果:pi: 3.14059

一つ増やし

#include <iostream>
using namespace std;

int main()
{
    double sum = 0, temp;
    int i=1;
    while(i <= 1001){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << "pi: " << (sum * 4) << endl;

    return 0;
}

結果:pi: 3.14259

高精度でやった方も
既出

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    long double sum = 0, temp;
    int i=1;
    while(i <= 1000000){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << fixed << setprecision(15) << "pi: " << (sum * 4) << endl;

    return 0;
}

結果

pi: 3.141591653589793

一つ先まで

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    long double sum = 0, temp;
    int i=1;
    while(i <= 1000001){
        temp = 1.0/(2*i-1);
        if (i%2 == 0){
            sum = sum - temp;
        }else{
            sum =sum + temp;
        }
        i++;
    }
    cout << fixed << setprecision(15) << "pi: " << (sum * 4) << endl;

    return 0;
}

結果

pi: 3.141593653588793

うん、すごく妥当でした。バグではありませんでした。式から、ちょうど、それぞれ1000の逆数、1000000の逆数のオーダーでプラスマイナス振れているのだからこれくらいの差が出るのは偶然ではなく必然ですね。
あとCopilot先生のアドバイスも全く適切なものでしたね。
疑ってごめんなさい(^_^;)

いいなと思ったら応援しよう!