中学生に説明するための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先生のアドバイスも全く適切なものでしたね。
疑ってごめんなさい(^_^;)