ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 사인, 코사인, 지수 함수를 c/c++로 계산하는 법
    컴퓨터 공학/c,c++ 2023. 1. 24. 04:23

    들어가며

    고등학교 시절에 삼각함수, 지수함수, 로그함수 등을 배우면서 항상 궁금했던 질문이 하나 있었습니다. 바로 이런 초월함수의 정확한 값은 어떻게 도출하는지에 대한 질문이었죠. 사인, 코사인 함수의 경우 특수각 $n\pi $에 대해서는 1이나 0으로 그 값을 쉽게 알 수 있지만 그 외의 값은 어떻게 정확히 알아 낼 수 있을까요? $log_{10}{100}=2$ 인 것을 쉽게 알 수 있지만 $log_{3}{100}$의 값을 실수로 표현하기는 쉽지는 않아 보입니다. 선생님께 질문하면 컴퓨터가 계산해준다고 말씀해주실 뿐이었죠. 그리고 대학에 와서 미적분 수업에 Taylor Series(테일러 급수), Maclaurin series(매클로린 급수)등에 대해 공부하면서 그 해답을 얻을 수 있었습니다. 이번 글에서는 사인, 코사인, 지수함수의 Maclaurin series(매클로린 급수)를 활용해 c/c++로 이를 계산하는 방법에 대해 알아보도록 하겠습니다. 

    (여담이지만 요즘 부쩍 수치해석이나 공학용 계산기 만들기 같은 이상한 뻘짓에 관심이 많아져 이런 주제를 가져와 봤어요ㅎㅎ)


    글을 시작하기 앞서 이번 포스팅에서 테일러 급수, 매클로린 급수에 대해 자세히 다루지 않습니다. 해당 내용에 대해 익숙하지 않으신 분들은 테일러, 매클로린 급수가 무엇인지 찾아보고 오시는 것도 추천 드립니다. 하지만 그냥 함수를 특정 범위(Radius of convergence: 수렴 반지름 혹은 수렴 반경)에서 급수(정확히는 Power series: 거듭 제곱 급수 혹은 멱급수) 형태로 표현할 수 있다라는 것 정도만 알고 넘어가도 될 것 같습니다. 

     

    테일러 급수 - 위키백과, 우리 모두의 백과사전

    위키백과, 우리 모두의 백과사전. 사인 함수의 테일러 급수의 수렴. 검은 선은 사인 함수의 그래프이며, 색이 있는 선들은 테일러 급수를 각각 1차(빨강), 3차(주황), 5차(노랑), 7차(초록), 9차(파랑

    ko.wikipedia.org

     

     

    사인 함수

    사인 함수의 매클로린 급수는 다음과 같습니다. 

    $$\sum_{n=0}^{\infty}\frac{(-1)^nx^{2n+1}}{(2n+1)!}$$

     

    이를 c++ 코드로 구현하면 다음과 같은 형태가 될 것입니다.

    double sin(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i+1))/factorial(2*i+1);
    	}
    	return ans;
    }

    여기서 n은 충분히 큰 수로 넣어 주면 됩니다. 수식에서도 알 수 있지만 매클로린 급수는 무한 급수입니다. 동시에 convergence(수렴)하는 함수 입니다. 따라서 적당히 큰수를  n에 넣어주면 실제 함수 값과 매우 유사하게 근사한 값을 반환하게 됩니다. 수렴하는 급수의 오차 추정은 다음에 기회가 되면 다루어 보도록 하겠습니다. 저는 여기서 사용자가 특별히 입력하지 않으면 자동으로 n에 100을 넣도록 코드를 짜주었지만 사실 100까지 가지 않아도 충분히 sin값을 근사할 수 있습니다.

     

    이때 factorial함수는 팩토리얼을 구해주는 함수로 다음과 같이 정의해 주었습니다. 이 함수는 코사인, 지수 함수 근사에도 똑같이 사용될 것입니다.

    double factorial(int x){
    	double ans = 1;
    	for(int i=1;i<=x;i++) ans*=i;
    	return ans;
    }

     

     

    코사인 함수

    코사인 함수의 매클로린 급수는 다음과 같습니다.

    $$\sum_{n=0}^{\infty}\frac{(-1)^nx^{2n}}{(2n)!}$$

     

    이를 c++ 코드로 구현하면 다음과 같은 형태가 될 것입니다.

    double cos(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i))/factorial(2*i);
    	}
    	return ans;
    }

     

     

    지수 함수

    지수 함수의 매클로린 급수는 다음과 같습니다.

    $$\sum_{n=0}^{\infty}\frac{x^n}{n!}$$

     

    이를 c++ 코드로 구현하면 다음과 같은 형태가 될 것입니다.

    double exp(double x, int n=100){
    	double ans=0;
    	for(int i=0;i<n;i++){
    		ans+=pow(x,i)/factorial(i);
    	}
    	return ans;
    }

     

    결과 비교

    결과가 잘 나오는지 확인을 해보고자 합니다. 사실 c++ 표준 라이브러리에 이미 sin, cos, exp(지수) 함수가 구현이 되어있습니다. 표준 라이브러리에서 제공하는 함수와 값이 똑같이 나오면 성공적인 구현이라고 할 수 있을 것입니다. 그러기 위해서는 우선 우리가 만든 함수들을 하나의 namespace(네임스페이스)로 묶어주어야 합니다. 왜나하면 우리가 만든 함수의 이름인 sin, cos, exp가 표준라이브러리에도 똑같은 이름으로 정의 되어 있기 때문에 네임스페이스를 이용해 이 둘을 구분해주어야 하기 때문이죠. 

    namespace Babyseal{
    double factorial(int x){
    	double ans = 1;
    	for(int i=1;i<=x;i++) ans*=i;
    	return ans;
    }
    double sin(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i+1))/factorial(2*i+1);
    	}
    	return ans;
    } 
    
    double cos(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i))/factorial(2*i);
    	}
    	return ans;
    } 
    
    double exp(double x, int n=100){
    	double ans=0;
    	for(int i=0;i<n;i++){
    		ans+=pow(x,i)/factorial(i);
    	}
    	return ans;
    }
    }

    이제 모든 준비가 끝났습니다. 표준 라이브러리에서 제공하는 함수들의 리턴값들과 우리가 만든 함수들의 리턴값을 비교해 봅시다.

     

    전체 코드

    #include<bits/stdc++.h>
    using namespace std;
    
    namespace Babyseal{
    double factorial(int x){
    	double ans = 1;
    	for(int i=1;i<=x;i++) ans*=i;
    	return ans;
    }
    double sin(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i+1))/factorial(2*i+1);
    	}
    	return ans;
    } 
    
    double cos(double x, int n=100){
    	double ans = 0;
    	for(int i=0;i<n;i++){
    		ans+=(pow(-1,i)*pow(x,2*i))/factorial(2*i);
    	}
    	return ans;
    } 
    
    double exp(double x, int n=100){
    	double ans=0;
    	for(int i=0;i<n;i++){
    		ans+=pow(x,i)/factorial(i);
    	}
    	return ans;
    }
    }
    
    int main(){
    	cout<<"사인 함수"<<endl;
        cout<<"sin(0)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::sin(0)<<" 표준 라이브러리에 있는 함수: "<<sin(0)<<endl;
        cout<<"sin(pi/2)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::sin(M_PI/2)<<" 표준 라이브러리에 있는 함수: "<<sin(M_PI/2)<<endl;
        cout<<"sin(pi)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::sin(M_PI)<<" 표준 라이브러리에 있는 함수: "<<sin(M_PI)<<endl;
        cout<<"sin(12)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::sin(12)<<" 표준 라이브러리에 있는 함수: "<<sin(12)<<endl<<endl;
        
        cout<<"코사인 함수"<<endl;
        cout<<"cos(0)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::cos(0)<<" 표준 라이브러리에 있는 함수: "<<cos(0)<<endl;
        cout<<"cos(pi/2)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::cos(M_PI/2)<<" 표준 라이브러리에 있는 함수: "<<cos(M_PI/2)<<endl;
        cout<<"cos(pi)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::cos(M_PI)<<" 표준 라이브러리에 있는 함수: "<<cos(M_PI)<<endl;
        cout<<"cos(12)"<<endl;
        cout<<fixed<<"우리가 만든 함수: "<<Babyseal::cos(12)<<" 표준 라이브러리에 있는 함수: "<<cos(12)<<endl<<endl;
        
        cout<<"지수 함수"<<endl;
        cout<<fixed<<Babyseal::exp(1.23)<<" "<<exp(1.23)<<endl;
    	return 0;
    }

     

    결과

    표준라이브러리와 우리가 만든 함수가 큰 차이가 없는 것을 확인 할 수 있다.

     


    마무리하며

    오늘은 사인, 코사인, 지수함수의 매클로린 급수를 바탕으로 c++에서 이를 구해보는 방법에 대해 알아보았습니다. 이 함수 외에도 로그함수, 역삼각함수, 쌍곡선함수 등 다른 초월함수들도 급수로 표현할 수 있고 이를 위와 같은 방법으로 c++함수로 구현할 수 있습니다. 오늘의 포스트는 여기서 마치겠습니다. 감사합니다!

    댓글

Designed by Tistory.