Taylor serise로 sin값을 구하는 코드가 cmath의 sin과 오차가 7%정도 됩니다.

awidesky의 이미지

Taylor serise로 sin값을 구하는 코드를 작성했는데, cmath에 있는 sin과 relative error(fabs( 1 - (getTaylorSin(i) / sin(i)) ))를 비교해 보니 7%정도의 차이가 나네요;;
floating point의 오차일 뿐일까요...?

double getTaylorSin(const double x) {
 
	if (x < 0)
		return -getTaylorSin(x);
	if (x > doubleTwoPI)                           //ask JVM to do modulus with double : no problem! :D
		return getTaylorSin(std::fmod(x, doubleTwoPI));  //ask C++ compiler to do modulus with double : F you I refuse to calculate &$^%&#$%@$&
	if (x > doublePI)
		return (-1) * getSin(x - doublePI);
	if (x > doubleHalfPI)
		return getTaylorSin(doublePI - x);
 
	int sign = -1;
	double divisor = 1;
	int i = 1;
	double num = 0;
	double dividend = x;
	double result = dividend;
 
	do {
 
		dividend = dividend * x * x * sign;
 
		i += 1;
		divisor *= i;
		i += 1;
		divisor *= i;
 
		num = dividend / divisor;
 
		result += num;
 
	} while (num >= epsillon);
 
	return result;
 
}

https://stackoverflow.com/questions/42217069/approximating-sinex-with-a-taylor-series-in-c-and-having-a-lot-of-problems
여기에 나오는 식을 이용했습니다. 혹시 implement 과정에서 오류가 있을지 궁금합니다!

익명 사용자의 이미지

전체 프로그램을 다 올려주세요. 테스트해 볼 수 있게.

1) doublePI를 어떻게 설정했는지, epsilon 값은 어떻게 줬는지,
2) 어떤 입력(x)을 줬는지
3) 어떤 결과가 나왔고, std::sin 결과와 어떻게 차이가 나는지

등등. 테스트하는 데 필요한 정보를 모두 올려주셔야 문제를 재현해보고 원인을 파악할 수 있겠지요.

일단 (Taylor serise를 제대로 구현했다는 가정하에) 가장 먼저 의심되는 부분은 doublePI가 충분히 정밀하지 않고, x를 너무 큰 값을 넣어준 경우입니다.

이 경우 fmod에서 오차가 발생하여 최종 연산 결과에 거의 고스란히 나타나게 됩니다.

익명 사용자의 이미지

테일러 시리즈는 무한 급수입니다.
정확한 값을 계산하는게 아니고, 그 값에 수렴해 갈 뿐입니다.
아마 epsillon 값을 작게 주면 더 정밀한 값이 나오지 않을까 합니다.

awidesky의 이미지

우선 다양한 가능성을 짚어 주신 분들 감사드립니다.

VS 2019 상에서의 테스트 결과도 주석으로 적어놓았습니다.

#include <iostream>
#include <time.h>
#include <math.h> //c에서 주로 사용하는 헤더로 알고 있는데 c++에서 사용하는 게 좋은 건지 모르겠네요;;
 
constexpr auto doubleTwoPI = 6.28318530718;
constexpr auto doublePI =    3.14159265359;
constexpr auto doubleHalfPI = 1.57079632679;
constexpr auto  a1Double = -0.467;
constexpr auto  a2Double = -0.4846785;
constexpr auto epsillon = 0.00001;
 
 
double getSin(const double x) { // 이전에 개인적으로 대충 만들어 본 근사 알고리즘입니다. 질문과는 관계없으나, taylor series 를 이용한 방법은 relative error가 0.07인 반면, 이 알고리즘은 이차함수 몇 개로 대충 근사했음에도  0.01정도인 게 이상합니다;;
 
	if (x < 0)
		return -getSin(x);
	if (x <= 0.236872109056403)
		return x;
	if (x > doubleTwoPI)                          
		return getSin(std::fmod(x, doubleTwoPI)); 
	if (x > doublePI)
		return (-1) * getSin(x - doublePI);
	if (x > doubleHalfPI)
		return getSin(doublePI - x);
 
	if (x < 0.591385707016756)
		return -0.1 * x * (x - doublePI) * (x + doublePI);
	if (x < 0.801774927340932)
		return a1Double * ((x - doubleHalfPI) * (x - doubleHalfPI)) + 1;
 
	return a2Double * ((x - doubleHalfPI) * (x - doubleHalfPI)) + 1;
 
}
 
double getTaylorSin(const double x) { //문제가 되는 함수의 구현부입니다.
 
	if (x < 0)
		return -getTaylorSin(x);
	if (x > doubleTwoPI)                          
		return getTaylorSin(std::fmod(x, doubleTwoPI)); 
	if (x > doublePI)
		return (-1) * getSin(x - doublePI);
	if (x > doubleHalfPI)
		return getTaylorSin(doublePI - x);
 
	int sign = -1;
	double divisor = 1;
	int i = 1;
	double num = 0;
	double dividend = x;
	double result = dividend;
 
	do {
 
		dividend = dividend * x * x * sign;
 
		i += 1;
		divisor *= i;
		i += 1;
		divisor *= i;
 
		num = dividend / divisor;
 
		result += num;
 
	} while (num >= epsillon);
 
	return result;
 
}
 
void testTime() // 라이브러리 함수와 시간을 비교합니다. 질문과 직접적 관계는 없습니다.
{
 
	const double testTo = doubleHalfPI;
 
	clock_t start, end;
	double result;
 
	start = clock();
 
	for (double i = 0.0; i < testTo; i += epsillon)
	{
		getTaylorSin(i);
	}
 
	end = clock();
	result = (double) (end - start);
	std::cout << "result : " << result << " ms in Taylor" << std::endl;
 
 
	start = clock();
 
	for (double i = 0.0; i < testTo; i += epsillon)
	{
		sin(i);
	}
 
	end = clock();
	result = (double)(end - start);
	std::cout << "result : " << result << " ms in library" << std::endl;
 
}
 
 
void testErr() //relative error를 계산합니다.
{
 
	double e, max = 0.0;
	const double testTo = doublePI;
 
	for (double i = 0.0; i < testTo; i += epsillon)
	{
		e = fabs( 1 - (getTaylorSin(i) / sin(i)) ); // 이 상태에서는 결과가 0.07, getTaylorSin자리에 getSin을 넣으면 0.01이 나옵니다.
		max = (e > max) ? e : max;
 
	}
 
	std::cout << max << std::endl;
}
 
 
 
int main()
{
 
	testErr();
	return 0;
 
}
익명 사용자의 이미지

흠...

1. getTaylorSin 함수에서 반복문을 나오는 조건은 absolute error에 걸려 있는데,
정작 정확도를 확인하는 방법은 relative error로군요.

아시다시피 참값의 절대값이 작을수록 absolute error 대비 relative error가 훨씬 크게 증폭됩니다.

2. 게다가 반복문을 보면 sin(x)에 들어갈 x 값이 꽤 작은 값부터 시작하는군요.

아시다피시 x이 매우 적으면 sin(x)는 거의 x입니다.
x에 epsilon을 넣고 돌리면 getTaylorSin 함수의 루프가 몇 번이나 돌겠어요?

awidesky의 이미지

double-pricision이라도 오차가 발생할 수밖에 없으니 relative error로 condition을 지정해 주었다가 num이 0이 되어도 조건을 만족하지 못해 무한루프를 돌지는 않을까 걱정되어서 absolute error를 썼었습니다.
아래와 같이 num변수(serise의 각 항)이 0이 될 때까지 돌리면 되지 않을까 생각합니다.(그 이후는 의미가 없으니...)

} while (num > 0.0);

또 답변 주신 이후 테스트를 한번 더 해봤습니다.

void testErr()
{
 
	double e, x, max = 0.0;
	const double testTo = doublePI;
 
	for (double i = 0.0; i < testTo; i += epsillon)
	{
		e = fabs( 1 - (getSin(i) / sin(i)) );
		//max = (e > max) ? e : max;
		if (e > max) { max = e; x = i; }
	}
 
	std::cout << max << " in " << x << std::endl; //in Taylor, result is 0.0751669 in 1.5708
	                                            //in approx, result is 0.00982811 in 2.55021
}

(공교롭게도 taylor에서 에러가 최댓값일 때(1.5708) sin 값이 거의 1이네요)
1이면 결과값의 절댓값이 작은 편은 아닌데;; 하는 의문이 듭니다.(sin의 최댓값이니까요)
답변 마지막 문단처럼 x가 매우 작을 때 에러가 크게 날 것이라고 생각했는데 이상하네요;;

여러 항의 합으로 구한 값보다 개형이 비슷한 2,3차 함수로 근사하는 것이 훨씬 정확하게 나오는 걸 보면 여러 수를 곱하고 나누는 과정에서 double의 오차가 축적되었다는 설명이 제일 논리적이지 않을까 생각이 듭니다.

awidesky의 이미지

double-pricision이라도 오차가 발생할 수밖에 없으니 relative error로 condition을 지정해 주었다가 num이 0이 되어도 조건을 만족하지 못해 무한루프를 돌지는 않을까 걱정되어서 absolute error를 썼었습니다.
아래와 같이 num변수(serise의 각 항)이 0이 될 때까지 돌리면 되지 않을까 생각합니다.(그 이후는 의미가 없으니...)

} while (num > 0.0);

또 답변 주신 이후 테스트를 한번 더 해봤습니다.

void testErr()
{
 
	double e, x, max = 0.0;
	const double testTo = doublePI;
 
	for (double i = 0.0; i < testTo; i += epsillon)
	{
		e = fabs( 1 - (getSin(i) / sin(i)) );
		//max = (e > max) ? e : max;
		if (e > max) { max = e; x = i; }
	}
 
	std::cout << max << " in " << x << std::endl; //in Taylor, result is 0.0751669 in 1.5708
	                                            //in approx, result is 0.00982811 in 2.55021
}

(공교롭게도 taylor에서 에러가 최댓값일 때(1.5708) sin 값이 거의 1이네요)
1이면 결과값의 절댓값이 작은 편은 아닌데;; 하는 의문이 듭니다.(sin의 최댓값이니까요)
답변 마지막 문단처럼 x가 매우 작을 때 에러가 크게 날 것이라고 생각했는데 이상하네요;;

여러 항의 합으로 구한 값보다 개형이 비슷한 2,3차 함수로 근사하는 것이 훨씬 정확하게 나오는 걸 보면 여러 수를 곱하고 나누는 과정에서 double의 오차가 축적되었다는 설명이 제일 논리적이지 않을까 생각이 듭니다.

익명 사용자의 이미지

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

getTaylorSin 함수의 do-while문 조건에서

num >= epsillon
=> abs(num) >= epsillon

으로 바꿔야지요.

익히 아시겠지만 sin 함수의 Taylor series는 양수와 음수가 교대로 나타납니다.

do-while문에 들어가면 num은 곧바로 음수가 되니 do-while문은 단 한 번만 반복되는 셈이고
결국 어떤 x에 대해서건 최대 3차항까지만 계산되겠네요.
조건을 num > 0.0으로 바꿔도 똑같은 문제입니다.

아오. 이걸 돌려 보고서야 알아채다니, 저도 실력 다 죽었네요.
공부 좀 하고 살아야겠습니다.

Anti-Lock의 이미지

sin 함수 구현을 했다면...
c 라이브러리의 sin 함수 결과와만 비교할게 아니라...
참값! 하고도 비교를 해야하지 않나 싶습니다.
수치해석용 수학 라이브러리의 존재의 이유가 있다면 분명 차이가 있을 겁니다.

awidesky의 이미지

감사드립니다. double에 대한 불신이 싹 날아갔네요, 대신 제 자신에 대한 불신이 무럭무럭 자라납니다 ㅋㅋㅋㅋㅋ

do {
 
		dividend = dividend * x * x * sign;
 
		i += 1;
		divisor *= i;
		i += 1;
		divisor *= i;
 
		num = dividend / divisor;
 
		result += num;
 
	} while (abs(num) > 0.0);

참값과 비교해 보라는 주언해주신 분 또한 역시 감사드립니다. cmath 의외에도 수치해석용 라이브러리가 따로 있나 보군요?(사실 c에 대해 제가 아직 익숙치 못합니다, 저 코드도 이전에 java로 짠 걸 포팅한 거라ㅎㅎ;;)
c에도 java의 BigDecimal과 비슷한 라이브러리가 있을 테니 해봐야겠네요, 감사합니다!

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.