프로그래밍실습 제 8 강
강의 내용 도전 프로그래밍 !!!(프로젝트 오일러) 무한루프 무한루프가 되는 이유 난수(random number)의 출력 난수 3개를 한 줄에 하나씩 출력하는 프로그램 1부터 6까지의 정수로 난수 n개를 출력하는 프로그램 숫자 맞추기 게임 몬테카를로 기법을 이용하여 원주율 의 근삿값 구하기 난수를 이용한 ASCII 문자 출력 순환문 복습: 원주율의 근삿값 구하기
도전 프로그래밍 !!! 프로젝트 오일러: http://euler.synap.co.kr (한글) https://projecteuler.net (영어) (여러 가지 다양한 프로그래밍 문제에 도전해 볼 수 있는 사이트) 프로젝트 오일러 소개 여기 실린 문제들을 풀려면 수학적인 지식이나 프로그래밍 솜씨 한가지만으로는 충분하지 않습니다. 수학적인 지식이 있다면 좀 더 우아하고 효율적인 방법을 찾을 수 있겠지만, 대부분의 경우 문제를 해결하려면 컴퓨터에 대한 지식과 프로그래밍 기법을 동원해야 합니다. 새로운 분야에 대한 탐구심을 키워가고, 재미있게 즐기는 중에 새로운 개념을 하나 둘 알아갈 수 있는 장을 마련하자는 것이 이 프로젝트를 시작해서 지금까지 이르게 된 동기라고 할 수 있겠습니다. 누구를 대상으로 하나요? 기본적인 교과과정만으로는 배움에 대한 욕구를 채우기 부족한 학생들로부터, 수학을 전공하지는 않았지만 수학적인 것에 관심이 있는 성인들, 그리고 스스로의 문제 해결 능력과 수학적 역량을 연마하고자 하는 전문가들이 모두 대상입니다.
무한루프: 순환문이 멈추지 않고 계속 실행되는 경우 아래 프로그램을 실행시켜 보고 무한루프가 되는 이유를 살펴보자. (if문에서 실수 끼리 비교하는 것이 위험한 것을 보여주는 예) #include<stdio.h> main() { double x=0., f; while(1){ x+=0.1; f=x*x+x+1.; printf("x=%.1f f=%.2f\n",x,f); if(x==1.0) break; } (원래 의도: x = 0.1, 0.2, 0.3, … , 1.0에서 x2 + x + 1 값을 각각 출력) 실행 결과: 무한 루프
무한루프가 되는 이유 double형 변수 x에 0.1 이 저장되었지만 2진수에서는 이 수가 무한소수이므로 정확한 0.1 이 아니다. 따라서 이 수를 반복하여 10번 더한다 하더라도 1.0과 같아지지 않는다. if 문에서 실수끼리 등호(==)를 사용하여 비교하는 것은 매우 위험하다. for나 while등의 순환문에서도 첨자는 항상 정수형을 사용하는 것이 안전하다.
해결 방법 (?): 부등호의 사용 - 완벽한 해결책은 아니다. 해결 방법 (?): 부등호의 사용 - 완벽한 해결책은 아니다. #include<stdio.h> main() { double x=0., f; while(1){ x+=0.1; f=x*x+x+1.; printf("x=%.1f f=%.2f\n",x,f); if(x>=1.0) break; }
실행 결과: 무한 루프는 피했으나 원래 의도와는 다르게 0.1부터 1.1까지의 값이 0.1간격으로 출력된다. x=0.1 f=1.11 x=0.2 f=1.24 x=0.3 f=1.39 x=0.4 f=1.56 x=0.5 f=1.75 x=0.6 f=1.96 x=0.7 f=2.19 x=0.8 f=2.44 x=0.9 f=2.71 x=1.0 f=3.00 x=1.1 f=3.31 이유: 반올림 오차 때문에 10번 더했을 때의 x값이 1보다 약간 작다.
해결 방법 : 정수형끼리 비교한다 !!! #include<stdio.h> main() { double x=0., f; int i=0; // 정수형 변수의 도입 while(1){ x+=0.1; f=x*x+x+1.; printf("x=%.1f f=%.2f\n",x,f); i++; if(i==10) //등호로 비교 할 때는 정수형을 사용 break; }
실행 결과: x=0.1 f=1.11 x=0.2 f=1.24 x=0.3 f=1.39 x=0.4 f=1.56 x=0.5 f=1.75 x=0.6 f=1.96 x=0.7 f=2.19 x=0.8 f=2.44 x=0.9 f=2.71 x=1.0 f=3.00
난수(random number)의 출력 이를 위해 stdlib.h 가 필요하다. 난수를 출력하기 위해서는 보통 srand()와 rand()를 동시에 사용하며 이를 위해 stdlib.h 가 필요하다. rand()를 이용하여 난수를 출력하며 난수는 0부터 32767(=215 -1) 까지의 정수중 하나가 임의로 출력된다. srand()를 이용하여 다음과 같이 초기화 하는데 이 부분을 생략하면 프로그램을 실행 할 때 마다 같은 난수 들이 출력되므로 주의해야 한다. srand(time(NULL)); 이 경우 time()을 사용하기 위하여 time.h가 필요하다. 난수의 범위를 적당히 조절하기 위해서는 나머지 연산자 %를 이용하면 편리하다. 예를 들어 1부터 6사이의 난수를 출력하려면 다음과 같이 한다: rand() % 6 + 1
난수 3개를 한 줄에 하나씩 출력하는 프로그램 #include<stdio.h> #include<stdlib.h> #include<time.h> main() { int n=3,i; unsigned short a; srand(time(NULL)); for(i=1;i<=n;i++){ a=rand(); printf("%u\n",a); } 참고: (1)여러 번 실행할 때 매번 다른 결과가 나오는가? (2) srand(time(NULL)); 을 지우면 어떻게 되는가?
1부터 6까지의 정수로 난수 n개를 출력하는 프로그램 #include<stdio.h> #include<stdlib.h> #include<time.h> main() { int a,n,i=1; printf("몇 개의 난수를 원하십니까? "); scanf("%d",&n); srand(time(NULL)); // 이 부분을 생략하면 실행할 때마다 같은 출력 while(i<=n){ a=rand(); if(a>=1 && a<=32766){ // 32766은 6의 배수임 a=a%6+1; printf("%d ",a); if(i%10==0 || i==n) printf("\n"); i++; }
숫자 맞추기 게임 숫자를 입력하십시오(1-20): 10 너무 작습니다. 숫자를 입력하십시오(1-20): 15 너무 큽니다. 아래 입출력과 같이 1부터 20까지의 숫자 중 하나의 난수를 생성하여 이를 맞추는 숫자 맞추기 게임을 작성해 보자. 숫자를 입력하십시오(1-20): 10 너무 작습니다. 숫자를 입력하십시오(1-20): 15 너무 큽니다. 숫자를 입력하십시오(1-20): 12 숫자를 입력하십시오(1-20): 14 숫자를 입력하십시오(1-20): 13 정답입니다.
#include<stdio.h> #include<stdlib.h> #include<time.h> main() { int ans,in,end=0; srand(time(NULL)); ans=rand()%20+1; do{ printf("숫자를 입력하십시오(1-20): "); scanf("%d",&in); if(in>ans) printf("너무 큽니다.\n"); else if(in<ans) printf("너무 작습니다.\n"); else{ printf("정답입니다.\n"); end=1; } } while(end!=1);
몬테카를로 기법을 이용하여 원주율(π) 의 근삿값 구하기 한 변의 길이가 2인 정사각형 안에 반지름이 1인 원이 내접하고 있다. 정사 각형 안에 속하는 임의의 점을 택할 때 이 점이 원 안에 속할 확률은 π /4 이다. 이를 이용하면 원주율의 근삿값을 구할 수 있다. 즉 (1) 자연수 n값을 입력 받는다. (2) 난수를 이용하여 순서쌍 (x,y)를 좌표로 갖는 점을 n개 생성한다. 단, x, y는 모두 구간 [-1,1]에 속하는 실수이다. 예를 들어 0부터 32767까지의 범위에 속하는 정수인 난수를 r이라 할 때 (2. / 32767.) * r – 1 을 계산하면 이는 [-1,1]에 속하는 실수이다. (3) 이중 x2 +y2 값이 1보다 작거나 같은 경우의 수가 m이라 하자. (이 경우 점은 원 안에 위치한다.) (4) π의 근삿값은 4 * m / n 이다. (n이 클수록 좀 더 정확한 근삿값이 된다.)
C코드: #include<stdio.h> #include<stdlib.h> #include<time.h> main() { int r1,r2,n,m,i; double x,y,c,pi; printf("n값 입력: "); scanf("%d",&n); srand(time(NULL)); c=2./32767.; // 반복 사용될 상수는 순환문 밖으로 m=0; for(i=1;i<=n;i++){ r1=rand(); r2=rand(); x=c*(double)r1-1.; // x 값은 [-1,1]에 속하는 실수 y=c*(double)r2-1.; // y 값은 [-1,1]에 속하는 실수 if(x*x+y*y<=1.) m++; } pi=4.*(double)m/(double)n; printf("pi=%f\n",pi);
참고: MS Visual C/C++의 경우 다음과 같이 rand_s 함수를 사용하면 0부터 UINT_MAX(=4,294,967,295)까지의 범위에 속하는 정수 값을 난수로 생성하므로 좀 더 정확한 계산이 된다. #define _CRT_RAND_S #include <stdlib.h> #include <stdio.h> #include <limits.h> main() { unsigned r1,r2,n,m,i; double x,y,c,pi; printf("n값 입력: "); scanf("%u",&n); c=2./UINT_MAX; m=0; for(i=1;i<=n;i++){ rand_s(&r1); rand_s(&r2); x=c*(double)r1-1.; y=c*(double)r2-1.; if(x*x+y*y<=1.) m++; } pi=4.*(double)m/(double)n; printf("pi=%f\n",pi);
다음 C코드의 출력을 보자. #include<stdio.h> main() { int i; for(i=33;i<=47;i++) printf("%c",i); printf("\n"); } 출력: !"#$%&'()*+,-./
난수를 이용하여 다음과 같이 21줄에 걸쳐 ASCII 문자표의 번호 33~47에 해당되는 문자들이 임의로 출력되도록 하는 프로그램을 작성하여 보자. ! $-& #(!-/ %)&//$. ,--)(+*// ''+"!%'%/!- '&*#))#&*%,$( *(#,//#%*#&$+)* (""&)$%/*!'(*&'%# *((,"!."(&$%.&')(%& +!(#/#+.$/,)"!)#-##&* #!"(,)*$*.*'$&'-"'))(.$ $+.%$'+"$.*$$-$)(/#!$#.(* &(.'.#./'!#!..!(&+#(%--%(-" ,-%+-$##+/).-")-*!()*&$/%%$"$ *)."-$*()$*&*/&("'$,-)$!)/("!$$ !!/&"'-/,+!%&/#-$%$,&&''"%%'&+-"' (!$'&+'$*"+.+'"*)##///'%-.!#//%#%&, #(/($#**/(!",.-)"%&,$(+&.!+++,.!+."&# -".!$+&(*/!.'!"((+,',.((#")+#!"'(,!%&(* $.&/*&($.-*#.+'.#.,*#&,+),.+%(",+'*$""("$
#include<stdio.h> #include<stdlib.h> #include<time.h> main() { int i,j,k; srand(time(NULL)); for(i=1;i<=21;i++){ for(j=1;j<=21-i;j++) printf(" "); k=2*i-1; for(j=1;j<=k;j++) printf("%c",rand()%15+33); printf("\n"); }
C코드: 05_13.c ~ 05_18a.c 참조
순환문 복습: 다음 식을 이용하여 원주율( )의 근삿값을 구해보자. 즉, 충분히 큰 자연수 N 에 대하여 다음 식을 이용하여 근삿값을 구할 수 있다. N=1,000,000,000 으로 두고 위의 식을 이용하여 원주율의 근삿값을 구하는 프로그램을 작성하여 보자.
#include<stdio.h> #include<math.h> #define N 1000000000 main() { int k; const double pi=acos(-1.); double a,pi2,error; double sum=0.; for(k=1;k<=N;k++){ a=1./(double)k; sum+=a*a; } pi2=sqrt(6.*sum); error=fabs(pi-pi2); printf("pi2=%.12f\n",pi2); //근삿값 printf("pi =%.12f\n",pi); //참값 printf("error=%.12e\n",error); //절대오차
출력: pi2=3.141592644982 pi =3.141592653590 error=8.607403234606e-009
참고: 다음과 같이 임을 이용하여 원주율의 근삿값을 계산하면 30항까지의 합만을 계산 하여도 오차가 매우 작게 나타남을 알 수 있다. #include<stdio.h> #include<math.h> #define N 30 void main() { int i,sign=1; const double pi=acos(-1.); double sum,x,xs,px,pi2,error; x=1./sqrt(3.); i=1; px=x; xs=x*x; sum=px; do{ i++; sign*=-1; px=px*xs; sum+=(double)sign*px/(2*i-1); } while(i<=N); pi2=6.*sum; error=fabs(pi-pi2); printf("pi2=%.12f\n",6.*sum); printf("pi =%.12f\n",pi); printf("error=%.12e\n",error); } 출력: pi2=3.141592653590 pi =3.141592653590 error=4.440892098501e-016