C는 sin () 및 기타 수학 함수를 어떻게 계산합니까?


248

.NET 디스 어셈블리와 GCC 소스 코드를 뚫고 있지만 실제 구현 sin()및 기타 수학 함수 를 찾을 수없는 것처럼 보입니다 ... 항상 다른 것을 참조하는 것 같습니다.

누구든지 그들을 찾도록 도울 수 있습니까? C가 실행할 모든 하드웨어가 하드웨어에서 삼각 함수를 지원하지 않을 것 같으므로 어딘가에 소프트웨어 알고리즘이 있어야합니다 .


함수 계산할 있는 몇 가지 방법을 알고 있으며 테일러 시리즈를 사용하여 함수를 계산하기 위해 내 자신의 루틴을 작성했습니다. 내 알고리즘이 꽤 영리하다고 생각하더라도 모든 구현이 항상 수십 배 느리기 때문에 실제 프로덕션 언어가 어떻게 작동하는지 궁금합니다.


2
이 구현은 종속적입니다. 가장 관심있는 구현을 지정해야합니다.
jason

3
두 곳을 모두 보았고 알아낼 수 없기 때문에 .NET과 C에 태그를 지정했습니다. .NET 디스 어셈블리를 살펴 보더라도 관리되지 않는 C를 호출하는 것처럼 보이지만 구현이 동일하다는 것을 알고 있습니다.
행크

답변:


213

GNU libm에서 구현 sin은 시스템에 따라 다릅니다. 따라서 각 플랫폼에 대한 sysdeps 의 적절한 하위 디렉토리에서 구현을 찾을 수 있습니다 .

하나의 디렉토리에는 IBM에서 제공 한 C 구현이 포함됩니다. 2011 년 10 월 이후 sin()로 일반적인 x86-64 Linux 시스템에서 호출 할 때 실제로 실행되는 코드입니다 . fsin조립 명령 보다 분명히 빠릅니다 . 소스 코드 : sysdeps / ieee754 / dbl-64 / s_sin.c 를 찾으십시오 __sin (double x).

이 코드는 매우 복잡합니다. 하나의 소프트웨어 알고리즘이 x 값 의 전체 범위에서 가능한 한 빠르고 정확하지 않으므로 라이브러리는 여러 가지 다른 알고리즘을 구현하며 첫 번째 작업은 x 를보고 사용할 알고리즘을 결정하는 것입니다.

  • x는 매우이다 매우 0에 가까운, sin(x) == x정답입니다.

  • 조금 더, sin(x)익숙한 Taylor 시리즈를 사용합니다. 그러나 이것은 0 근처에서만 정확하므로 ...

  • 각도가 약 7 °를 초과하면 sin (x) 및 cos (x) 모두에 대한 Taylor 계열 근사값을 계산 한 다음 사전 계산 된 테이블의 값을 사용하여 근사값을 세분화하는 다른 알고리즘이 사용됩니다.

  • 언제 | x | > 2, 위의 알고리즘 중 어느 것도 작동하지 않으므로 코드는 0에 더 가까운 값을 계산하여 시작 sin하거나 cos대신 할 수 있습니다 .

  • x 를 NaN 또는 무한대 로 처리하는 또 다른 브랜치가 있습니다.

이 코드는 전에는 본 적이없는 숫자 핵을 사용하지만 부동 소수점 전문가들 사이에서 잘 알려져있을 것입니다. 때로는 몇 줄의 코드가 설명하기 위해 여러 단락이 필요할 수도 있습니다. 예를 들어이 두 줄

double t = (x * hpinv + toint);
double xn = t - toint;

x 를 π / 2의 배수, 특히 × π / 2 의 배수만큼 x 와 0에 가까운 값 으로 줄이는 데 사용됩니다 (때로는) xn. 나누거나 분기하지 않고이 작업을 수행하는 방법은 다소 영리합니다. 그러나 전혀 의견이 없습니다!


이전 32 비트 버전의 GCC / glibc는 fsin명령을 사용 했는데, 일부 입력에서는 놀랍도록 정확하지 않습니다. 있다 코드의 단지 2 라인이 보여주는 매혹적인 블로그 게시물 .

sin순수 C에서 fdlibm의 구현은 glibc보다 훨씬 간단하며 잘 설명되어 있습니다. 소스 코드 : fdlibm / s_sin.cfdlibm / k_sin.c


35
이것이 실제로 x86에서 실행되는 코드임을 확인하려면 다음을 호출하는 프로그램을 컴파일하십시오 sin(). 입력 gdb a.out한 다음 break sin, 다음 run, 다음 disassemble.
Jason Orendorff

5
@ 헨리 : 좋은 코드라고 생각하는 실수를하지 마십시오. 정말 끔찍합니다 . 그렇게 코딩하는 법을 배우지 마십시오!
토마스 보니 니

2
@Andreas Hmm, 맞습니다. IBM 코드는 fdlibm에 비해 상당히 끔찍합니다. fdlibm의 사인 루틴에 대한 링크를 추가하기 위해 답변을 편집했습니다.
Jason Orendorff

3
@Henry : __kernel_sin는 k_sin.c 에 정의되어 있으며 순수한 C입니다. 다시 클릭하십시오. URL을 처음으로 옮겼습니다.
Jason Orendorff

3
연결된 sysdeps 코드는 올바르게 반올림되므로 특히 흥미 롭습니다. 즉, 최근에야 가능해진 모든 입력 값에 대해 가장 적합한 답을 제공합니다. 올바른 반올림을 보장하기 위해 많은 추가 자릿수를 계산해야하기 때문에 경우에 따라 속도가 느려질 수 있습니다. 다른 경우에는 매우 빠릅니다. 숫자가 작 으면 답은 각도입니다.
Bruce Dawson

67

사인 및 코사인과 같은 기능은 마이크로 프로세서 내부의 마이크로 코드로 구현됩니다. 예를 들어, 인텔 칩에는 이에 대한 조립 지침이 있습니다. AC 컴파일러는 이러한 어셈블리 명령어를 호출하는 코드를 생성합니다. 반면에 Java 컴파일러는 그렇지 않습니다. Java는 하드웨어가 아닌 소프트웨어에서 삼각 함수를 평가하므로 훨씬 느리게 실행됩니다.

테일러 시리즈를 사용하여 삼각 함수를 계산하지는 않습니다. 우선 그들은 CORDIC 을 사용 하지만 짧은 Taylor 시리즈를 사용하여 CORDIC의 결과를 연마하거나 매우 작은 각도에서 높은 상대 정확도를 가진 사인을 계산하는 것과 같은 특수한 경우에 사용할 수 있습니다. 자세한 설명은이 StackOverflow 답변을 참조하십시오 .


10
사인 및 코사인과 같은 초월 수학 함수는 현재 32 비트 데스크톱 및 서버 프로세서에서 마이크로 코드 또는 하드웨어 명령어로 구현 될 수 있습니다. i486 (DX)의 모든 부동 소수점 계산이 별도의 보조 프로세서없이 x86 시리즈 용 소프트웨어 ( "소프트 플로트")에서 수행 될 때까지 항상 그런 것은 아닙니다. 모든 (FPU)가 초월 적 기능을 포함하지는 않았다 (예 : Weitek 3167).
mctylr

1
더 자세하게 얘기해 주 시겠어요? Taylor 시리즈를 사용하여 근사치를 어떻게 "고정화"합니까?
행크

4
답을 "연마"하는 한 사인과 코사인을 모두 계산한다고 가정합니다. 한 지점에서 정확한 값을 알고 있지만 (예 : CORDIC에서) 가까운 지점에서 값을 원한다고 가정합니다. 그런 다음 작은 차이 h에 대해 Taylor 근사값을 적용 할 수 있습니다 f (x + h) = f (x) + h f '(x) 또는 f (x + h) = f (x) + h f'(x) + h ^ 2 f ''(x) / 2.
John D. Cook

6
x86 / x64 칩에는 사인 (fsin)을 계산하기위한 조립 명령이 있지만이 명령은 때때로 부정확하므로 더 이상 사용되지 않습니다. 자세한 내용은 randomascii.wordpress.com/2014/10/09/… 를 참조하십시오. 대부분의 다른 프로세서 에는 사인 및 코사인에 대한 명령어 가 없습니다 . 소프트웨어에서 계산할 때 더 많은 유연성을 제공하고 더 빠를 수 있기 때문입니다.
Bruce Dawson

3
인텔 칩 내부의 코디네이터는 일반적으로 사용되지 않습니다. 첫째, 작업의 정확성과 해상도는 많은 응용 분야에서 매우 중요합니다. Cordic은 7 자리 정도가되면 예측할 수 없을 정도로 부정확합니다. 둘째, 구현에 버그가있어 더 많은 문제가 발생한다고 들었습니다. 나는 리눅스 gcc에 대한 sin 함수를 살펴 보았고, 확실히 chebyshev를 사용합니다. 내장 된 물건은 사용되지 않습니다. 또한 칩의 코디 알고리즘은 소프트웨어 솔루션보다 느립니다.
Donald Murray

63

얘들 아, 전문가들을위한 시간 .... 이것은 경험이없는 소프트웨어 엔지니어들에게 가장 큰 불만 중 하나입니다. 그들은 테일러 시리즈를 사용하여 초월적인 기능을 처음부터 계산해 왔습니다. 사실이 아니다. 이것은 잘 정의 된 문제이며 매우 영리한 소프트웨어 및 하드웨어 엔지니어가 수천 번 접근했으며 잘 정의 된 솔루션을 보유하고 있습니다. 기본적으로 초월 함수의 대부분은 Chebyshev 다항식을 사용하여 계산합니다. 사용되는 다항식은 상황에 따라 다릅니다. 먼저,이 문제에 대한 성경은 Hart and Cheney의 "Computer Approximations"라는 책입니다. 이 책에서 하드웨어 가산기, 곱셈기, 분배기 등이 있는지 여부를 결정하고 가장 빠른 연산을 결정할 수 있습니다. 예를 들어 정말 빠른 분배기가 있다면 사인을 계산하는 가장 빠른 방법은 P1 (x) / P2 (x) 일 수 있습니다. 여기서 P1, P2는 체비 쇼프 다항식입니다. 빠른 분할기가 없으면 P (x) 일 수 있습니다. 여기서 P는 P1 또는 P2보다 훨씬 더 많은 항을 갖습니다. 따라서 속도가 느립니다. 따라서 첫 번째 단계는 하드웨어와 수행 할 수있는 작업을 결정하는 것입니다. 그런 다음 Chebyshev 다항식의 적절한 조합을 선택합니다 (일반적으로 코사인의 경우 cos (ax) = aP (x) 형식, 예를 들어 P는 Chebyshev 다항식 임). 그런 다음 원하는 십진 정밀도를 결정합니다. 예를 들어 7 자리의 정밀도를 원한다면, 내가 언급 한 책의 해당 표를 찾아 보면 (정밀도의 경우 7.33) 숫자 N = 4와 다항식 숫자 3502를 얻게됩니다. N은 N = 4이므로 다항식 (p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0)입니다. 그런 다음 p4, p3, p2, p1의 실제 값을 찾습니다. 3502 미만의 책 뒷면에있는 p0 값 (부동 소수점에 있음). 그런 다음 (((p4.x + p3) .x + p2) .x + p1) .x + p0 ....의 형식으로 소프트웨어에서 알고리즘을 구현합니다. 이것은 코사인을 10 진수로 계산하는 방법입니다. 해당 하드웨어에 배치합니다.

FPU에서 초월 작업의 대부분의 하드웨어 구현에는 일반적으로 일부 마이크로 코드 및 이와 같은 작업이 포함됩니다 (하드웨어에 따라 다름). 체비 쇼프 다항식은 대부분의 초월에 사용되지만 전부는 아닙니다. 예를 들어, 제곱근은 먼저 룩업 테이블을 사용하여 Newton raphson 방법의 이중 반복을 사용하는 것이 더 빠릅니다. 다시, "컴퓨터 근사"라는 책에서 그 사실을 알 수 있습니다.

이러한 기능을 함양 할 계획이라면 누구든지 그 책의 사본을받는 것이 좋습니다. 실제로 이런 종류의 알고리즘에 대한 성경입니다. cordics 등과 같은 이러한 값을 계산하는 여러 가지 대체 수단이 있지만 정확도가 낮은 특정 알고리즘에 가장 적합한 경향이 있습니다. 매번 정밀도를 보장하기 위해 체비 쇼프 다항식이 진행됩니다. 내가 말했듯이 잘 정의 된 문제. 지금은 50 년 동안 해결되었습니다.

이제 말하면 Chebyshev 다항식을 사용하여 낮은 다항식으로 단일 정밀도 결과를 얻을 수있는 기술이 있습니다 (위의 코사인의 예와 같이). 그런 다음 "Gal 's Accurate Tables Method"와 같이 훨씬 큰 다항식을 사용하지 않고도 정확도를 높이기 위해 값을 보간하는 다른 기술이 있습니다. 후자의 기술은 ACM 문헌을 참조하는 포스트가 참조하는 것입니다. 그러나 궁극적으로 Chebyshev 다항식은 90 %의 방법을 사용하는 데 사용됩니다.

즐겨.


6
처음 몇 문장에 대해서는 더 동의 할 수 없었습니다. 또한, 정밀성을 보장하는 특수 기능을 계산하는 것은 어려운 문제 라는 점을 상기 할 가치가 있습니다 . 당신이 언급 한 영리한 사람들은이 일을 대부분의 삶에 보냅니다. 또한 기술적 인 측면에서 볼 때 Min-max 다항식은 추구하는 그레인이며 Chebyshev 다항식은 더 간단한 프록시입니다.
Alexandre C.

161
-1 비전문적이고 비틀 거리는 (그리고 약간 무례한) 톤, 그리고이 답변 의 실제 비 중복적인 내용 은 비틀 거리고 비난을 없애고, 기본적으로 "그들은 종종 Chebyshev 다항식을 사용합니다.이 책을 참조하십시오" 자세한 내용은 정말 좋습니다! " 아시다시피, 절대적으로 옳을 수도 있지만 실제로 여기에 원하는 자체 포함 된 답변 은 아닙니다 . 그런 식으로 요약하면 질문에 대해 적절한 의견을 제시했을 것입니다.
Ilmari Karonen

2
초기 게임 개발 초기에는 일반적으로 속도에 대한 룩업 테이블이 필요했습니다. 우리는 일반적으로 그러한 것들에 표준 lib 함수를 사용하지 않았습니다.
topspin

4
임베디드 시스템에서 조회 테이블을 자주 사용하고 라디안 대신 bittians를 사용하지만 게임과 같은 특수 응용 프로그램을위한 것입니다. 나는 그 사람이 c 컴파일러가 부동 소수점 숫자에 대한 죄를 계산하는 방법에 관심이 있다고 생각합니다 ....
Donald Murray

1
아, 50 년 전 나는 McLaren 시리즈로 Burroughs B220에서 그와 함께 연주하기 시작했습니다. 이후 CDC 하드웨어와 Motorola 68000. Arcsin은 지저분했습니다. 저는 두 개의 다항식의 몫을 선택하고 최적의 계수를 찾기 위해 코드를 개발했습니다.
Rick James

15

의 경우 sin특히, 테일러 전개를 사용하여 당신을 줄 것이다 :

sin (x) : = x-x ^ 3 / 3! + x ^ 5 / 5! -x ^ 7 / 7! + ... (1)

이들 사이의 차이가 허용 공차 수준보다 작거나 유한 한 단계 (빠르지 만 정확도는 낮음) 일 때까지 항을 계속 추가합니다. 예를 들면 다음과 같습니다.

float sin(float x)
{
  float res=0, pow=x, fact=1;
  for(int i=0; i<5; ++i)
  {
    res+=pow/fact;
    pow*=-1*x*x;
    fact*=(2*(i+1))*(2*(i+1)+1);
  }

  return res;
}

참고 : (1)은 작은 각도에 대한 근사 sin (x) = x 때문에 작동합니다. 각도가 클수록 허용 가능한 결과를 얻기 위해 점점 더 많은 항을 계산해야합니다. while 인수를 사용하여 특정 정확도를 계속 유지할 수 있습니다.

double sin (double x){
    int i = 1;
    double cur = x;
    double acc = 1;
    double fact= 1;
    double pow = x;
    while (fabs(acc) > .00000001 &&   i < 100){
        fact *= ((2*i)*(2*i+1));
        pow *= -1 * x*x; 
        acc =  pow / fact;
        cur += acc;
        i++;
    }
    return cur;

}

1
계수를 약간 조정하고 (다항식으로 하드 코딩하면) 약 2 회 더 빨리 중지 할 수 있습니다.
Rick James

14

예, 계산을위한 소프트웨어 알고리즘 sin도 있습니다. 기본적으로 디지털 컴퓨터로 이러한 종류의 물건을 계산하는 것은 일반적으로 함수를 나타내는 Taylor 시리즈를 근사화하는 것과 같은 수치 방법을 사용하여 수행 됩니다.

숫자 방법은 함수를 임의의 정확도로 근사 할 수 있으며 부동 숫자의 정확도는 유한하기 때문에 이러한 작업에 매우 적합합니다.


12
실제 구현에는 더 효율적인 방법이 있기 때문에 Taylor 시리즈를 사용하지 않을 것입니다. 도메인 [0 ... pi / 2]에서만 정확하게 근사하면되며 Taylor 시리즈보다 효율적으로 근사치를 제공하는 기능이 있습니다.
David Thornley

2
@David : 동의합니다. 나는 대답에서 "like"라는 단어를 언급 할만큼 충분히주의했다. 그러나 Taylor 확장은 함수를 근사화하는 메소드의 개념을 설명하는 간단한 것입니다. 즉, Taylor 시리즈를 사용한 소프트웨어 구현 (최적화인지 확실하지 않음)을 보았습니다.
Mehrdad Afshari

1
실제로 다항식 근사는 삼각 함수를 계산하는 가장 효율적인 방법 중 하나입니다.
Jeremy Salwen

13

Taylor 시리즈를 사용 하고 시리즈의 용어 사이의 관계를 찾아서 다시 계산하지 않도록하십시오.

cosinus의 예는 다음과 같습니다.

double cosinus(double x, double prec)
{
    double t, s ;
    int p;
    p = 0;
    s = 1.0;
    t = 1.0;
    while(fabs(t/s) > prec)
    {
        p++;
        t = (-t * x * x) / ((2 * p - 1) * (2 * p));
        s += t;
    }
    return s;
}

이것을 사용하여 이미 사용한 것을 사용하여 합계의 새로운 항을 얻을 수 있습니다 (계승 및 x 2p 피하기 )

설명


2
TeX를 사용하여 Google Chart API를 사용하여 이와 같은 수식을 만들 수 있다는 것을 알고 있습니까? code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer 2013

11

복잡한 질문입니다. x86 제품군의 Intel 계열 CPU는 하드웨어로 sin()기능을 구현 하지만 x87 FPU의 일부이며 64 비트 모드 (SSE2 레지스터가 대신 사용됨)에서 더 이상 사용되지 않습니다. 이 모드에서는 소프트웨어 구현이 사용됩니다.

몇 가지 그러한 구현이 있습니다. 하나에 fdlibm에 있으며 Java에서 사용됩니다. 내가 아는 한 glibc 구현에는 fdlibm의 일부와 IBM이 제공 한 다른 부분이 포함되어 있습니다.

초월 함수의 소프트웨어 구현은 sin()일반적으로 종종 Taylor 시리즈에서 얻은 다항식에 의한 근사를 사용합니다.


3
SSE2 레지스터는 x86 또는 x64 모드에서 sin ()을 계산하는 데 사용 되지 않으며 물론 sin은 모드에 관계없이 하드웨어에서 계산됩니다. 이봐, 그것은 :) 우리가 살고있는 2010의
이고르 Korkhov

7
@ 아이고 : 그것은 당신이보고있는 수학 라이브러리에 달려 있습니다. 그것은 x86 용 사용 SSE 소프트웨어 구현에 가장 최적화 된 수학 라이브러리 밝혀 sincos빠른 FPU의 하드웨어 지침보다. 더 단순하고 순진한 라이브러리는 fsinfcos명령어 를 사용하는 경향이 있습니다 .
Stephen Canon

@Stephen Canon : FPU 레지스터와 같이 빠른 라이브러리의 정밀도는 80 비트입니까? 나는 그들이 정밀도보다 속도를 선호한다는 매우 비열한 의심을 가지고 있습니다. 물론 예를 들어 게임과 같은 많은 시나리오에서 합리적입니다. 그리고 SSE 및 사전 계산 된 중간 테이블을 사용하여 32 비트 정밀도로 사인을 계산하는 것이 FSIN전체 정밀도 를 사용 하는 것보다 빠를 것이라고 생각합니다 . 빠른 라이브러리의 이름을 말해 주시면 감사하겠습니다.
Igor Korkhov

@ Igor : 64 비트 모드의 x86, 적어도 내가 아는 모든 유닉스 계열 시스템에서 정밀도는 x87 FPU의 79 비트가 아닌 64 비트로 제한됩니다. 의 소프트웨어 구현은 계산하는 sin()것보다 약 두 배 빠릅니다 fsin(정확히 덜 정확하게 수행되기 때문에). x87은 발표 된 79 비트보다 실제 정밀도가 약간 낮습니다.
Thomas Pornin

1
실제로 msvc 런타임 라이브러리에서 sin ()의 32 비트 및 64 비트 구현은 FSIN 명령어를 사용 하지 않습니다 . 사실, 그들은 다른 결과를 내고 예를 들어 sin (0.70444454416678126)을 취합니다. 이로 인해 32 비트 프로그램에서 0.64761068800896837 (오른쪽 0.5 * (eps / 2) 허용 오차)이 발생하고 64 비트 프로그램에서는 0.64761068800896848 (잘못된)이 발생합니다.
e.tadeu

9

다른 답변에서 언급했듯이 체비 쇼프 다항식은 함수와 다항식의 가장 큰 차이가 가능한 작은 다항식입니다. 그것은 훌륭한 시작입니다.

경우에 따라 최대 오류는 관심있는 것이 아니라 최대 상대 오류입니다. 예를 들어 사인 함수의 경우 x = 0 근처의 오차는 큰 값보다 훨씬 작아야합니다. 작은 상대 오류가 필요합니다. 따라서 sin x / x에 대한 Chebyshev 다항식을 계산하고 그 다항식에 x를 곱합니다.

다음으로 다항식을 평가하는 방법을 알아 내야합니다. 중간 값이 작고 반올림 오류가 작도록 평가하려고합니다. 그렇지 않으면 반올림 오류가 다항식의 오류보다 훨씬 커질 수 있습니다. 그리고 사인 함수와 같은 함수를 사용하면 부주의하면 sin x에 대해 계산 한 결과가 x <y 일 때도 sin y에 대한 결과보다 클 수 있습니다. 따라서 반올림 오차에 대한 계산 순서 및 상한 계산을 신중하게 선택해야합니다.

예를 들어, sin x = x-x ^ 3 / 6 + x ^ 5 / 120-x ^ 7 / 5040 ... 순진하게 계산하면 sin x = x * (1-x ^ 2 / 6 + x ^ 4 / 120 - X ^ / 5040 ... 6) 다음 괄호 기능이 저하되어, 그것이 것이다 y는 x에 다음으로 큰 번호 인 경우, 가끔 Y가 죄 X보다 작은 것 죄 것이 일어난다. 대신 이것이 발생할 수없는 sin x = x-x ^ 3 * (1/6-x ^ 2 / 120 + x ^ 4 / 5040 ...)를 계산하십시오.

Chebyshev 다항식을 계산할 때는 일반적으로 계수를 배정 밀도로 반올림해야합니다. 그러나 체비 쇼프 다항식은 최적이지만, 배정 밀도로 반올림 된 계수를 갖는 체비 쇼프 다항식은 배정 밀도 계수를 갖는 최적의 다항식이 아닙니다!

예를 들어 x, x ^ 3, x ^ 5, x ^ 7 등에 대한 계수가 필요한 sin (x)의 경우 다음을 수행합니다. 다항식 (ax + bx ^ 3 +으로 sin x의 최적 근사값을 계산합니다. cx ^ 5 + dx ^ 7)를 배정 밀도보다 높으면 a에서 배정 밀도로 반올림하여 A를 제공합니다. 이제 다항식 (bx ^ 3 + cx ^ 5 + dx ^ 7)을 사용하여 (sin x-Ax)의 최적 근사값을 계산하십시오. a와 A의 차이에 적응하기 때문에 다른 계수를 얻게됩니다. b를 배정 밀도 B로 반올림 한 다음 다항식 cx ^ 5 + dx ^ 7 등으로 근사값 (sin x-Ax-Bx ^ 3)을 구합니다. 원래 Chebyshev 다항식과 거의 같은 다항식을 얻을 수 있지만 Chebyshev가 배정 밀도로 반올림 한 것보다 훨씬 낫습니다.

다음으로 다항식을 선택할 때 반올림 오류를 고려해야합니다. 다항식 무시 반올림 오류에서 최소 오차가있는 다항식을 찾았지만 다항식 + 반올림 오차를 최적화하려고합니다. 체비 쇼프 다항식이 있으면 반올림 오차의 범위를 계산할 수 있습니다. f (x)는 함수이고 P (x)는 다항식이며 E (x)는 반올림 오차입니다. 최적화하고 싶지 않습니다 | f (x)-P (x) |, 최적화하려고합니다 | f (x)-P (x) +/- E (x) |. 반올림 오차가 큰 곳에서 다항식 오차를 유지하려고 시도하고 반올림 오차가 작은 곳에서 다항식 오차를 약간 완화하는 약간 다른 다항식을 얻게됩니다.

이 모든 것을 통해 마지막 비트의 최대 0.55 배의 반올림 오류를 쉽게 얻을 수 있습니다. +,-, *, /는 마지막 비트의 최대 0.50 배의 반올림 오류가 있습니다.


1
이것은 하나의 방법의 좋은 설명입니다 수 있습니다 효율적으로 죄 (X)를 계산하지만 정말 C 라이브러리 / 컴파일러가 어떻게 일반에 대한 구체적 인 OP의 질문에 대답하지 않는 것 않습니다 를 계산합니다.
Ilmari Karonen

체비 쇼프 다항식은 구간에 대한 최대 절대 값을 최소화하지만 목표 함수와 다항식 간의 최대 차이를 최소화하지는 않습니다. Minimax 다항식이 그렇게합니다.
Eric Postpischil

9

같은 삼각 함수에 관한 sin(), cos(), tan(): 고품질의 삼각 함수의 중요한 측면의 5 년 후, 언급이 없었다 범위 감소 .

이러한 기능 중 하나의 초기 단계는 각도를 라디안 단위로 2 * π 간격의 범위로 줄이는 것입니다. 그러나 π는 비합리적이므로 x = remainder(x, 2*M_PI)도입 오류 M_PI또는 기계 pi 와 같은 간단한 축소 는 π의 근사치입니다. 어떻게해야합니까 x = remainder(x, 2*π)?

초기 라이브러리는 확장 된 정밀도 또는 제작 된 프로그래밍을 사용하여 품질 결과를 제공했지만 여전히 범위가 제한되어 있습니다 double. 큰 값이 같은 요청 된 경우 sin(pow(2,30)), 결과는 나 의미가 있었다 0.0과 어쩌면과 오류 플래그 와 같은 값으로 설정 TLOSS정밀도 또는 총 손실 PLOSS정밀도 부분 손실.

-π에서 π와 같은 간격으로 큰 값을 적절히 범위 축소하는 것은 sin()자체 처럼 기본 삼각 함수의 문제와 경쟁하는 어려운 문제입니다 .

좋은 주장은 큰 논증에 대한 인수 축소입니다 : 마지막 비트에 적합 (1992). 그것은 잘 문제를 다룹의 필요성에 대해 설명하고 상황이 다양한 플랫폼 (SPARC, PC, HP, 30 + 기타)에 있었고,이 품질 결과를 제공하는 솔루션 알고리즘을 제공하는 방법 을 모두 double 에서 -DBL_MAX로를 DBL_MAX.


원래의 인수가 도수이지만 큰 값을 가질 수있는 경우, fmod()정밀도를 높이 려면 먼저 사용하십시오 . 좋은은 fmod()소개하지 않습니다 오류를 그렇게 훌륭한 범위의 감소를 제공한다.

// sin(degrees2radians(x))
sin(degrees2radians(fmod(x, 360.0))); // -360.0 < fmod(x,360) < +360.0

다양한 삼각 정체성과 remquo()훨씬 더 개선을 제공합니다. 샘플 : sind ()


6

라이브러리 함수의 실제 구현은 특정 컴파일러 및 / 또는 라이브러리 공급자에 달려 있습니다. 하드웨어 또는 소프트웨어에서 수행되는지, Taylor 확장인지 여부에 따라 다릅니다.

나는 그것이 전혀 도움이되지 않는다는 것을 알고 있습니다.


5

그것들은 일반적으로 소프트웨어로 구현되며 대부분의 경우 해당 하드웨어 (즉, 무시할만한) 호출을 사용하지 않습니다. 그러나 Jason이 지적했듯이 구현에 따라 다릅니다.

이 소프트웨어 루틴은 컴파일러 소스의 일부가 아니라 clib 또는 GNU 컴파일러의 glibc와 같은 해당 라이브러리에서 찾을 수 있습니다. 참조 http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions를

더 세밀하게 제어하려면 정확히 필요한 것을 신중하게 평가해야합니다. 일반적인 방법 중 일부는 조회 테이블의 보간, 어셈블리 호출 (종종 속도가 느림) 또는 제곱근에 대한 Newton-Raphson과 같은 다른 근사법입니다.


5

하드웨어가 아닌 소프트웨어로 구현하려는 경우이 질문에 대한 명확한 답을 찾을 수있는 곳은 Numerical Recipes의 5 장 입니다. 내 사본은 상자에 들어 있으므로 세부 정보를 제공 할 수는 없지만 짧은 버전 (이 권리를 기억하는 경우)은 tan(theta/2)기본 작업으로 가져 와서 거기에서 다른 것을 계산하는 것입니다. 계산은 시리즈 근사로 수행되지만 Taylor 시리즈보다 훨씬 빠르게 수렴 됩니다.

책에 손을 대지 않고 더 이상 남을 수는 없습니다.


5

소스를 치고 누군가가 라이브러리에서 실제로 사용하는 방법을 보는 것과 같은 것은 없습니다. 특히 하나의 C 라이브러리 구현을 살펴 보겠습니다. 나는 uLibC를 선택했습니다.

sin 함수는 다음과 같습니다.

http://git.uclibc.org/uClibc/tree/libm/s_sin.c

몇 가지 특별한 경우를 처리 한 다음 인수 축소를 수행하여 입력을 [-pi / 4, pi / 4] 범위에 매핑합니다 (인수를 큰 부분과 꼬리의 두 부분으로 나눕니다) 전화하기 전에

http://git.uclibc.org/uClibc/tree/libm/k_sin.c

그런 다음 두 부분에서 작동합니다. 꼬리가 없으면 대략 13 도의 다항식을 사용하여 근사값이 생성됩니다. 꼬리가 있으면 다음과 같은 원리에 따라 작은 교정 덧셈을 얻습니다.sin(x+y) = sin(x) + sin'(x')y


4

이러한 기능이 평가 될 때마다 어느 정도는 다음 중 하나 일 가능성이 높습니다.

  • 보간되는 값 테이블 (예 : 컴퓨터 그래픽과 같은 빠르고 부정확 한 응용 프로그램의 경우)
  • 원하는 값 --- 아마 수렴하는 일련의 평가 되지 테일러 시리즈, 가능성이 뭔가 Clenshaw - 커티스 같은 멋진 직교를 기반으로.

하드웨어 지원이없는 경우 컴파일러는 후자를 사용하여 ac 라이브러리를 사용하지 않고 어셈블러 코드 만 (디버그 기호없이) 만 방출합니다. 디버거에서 실제 코드를 추적하기가 까다로워집니다.


4

많은 사람들이 지적했듯이 구현에 따라 다릅니다. 그러나 귀하의 질문을 이해하는 한 , 수학 함수 의 실제 소프트웨어 구현에 관심이 있었지만 찾지 못했습니다. 이 경우 다음과 같습니다.

  • http://ftp.gnu.org/gnu/glibc/ 에서 glibc 소스 코드를 다운로드 하십시오.
  • 파일을 봐 dosincos.c에있는 압축을 푼의 glibc 루트 \ sysdeps \ IEEE754 \ DBL-64 폴더
  • 마찬가지로 나머지 수학 라이브러리의 구현을 찾을 수 있습니다. 적절한 이름의 파일을 찾으십시오.

.tbl확장명을 가진 파일을 살펴볼 수도 있습니다. 내용은 이진 형식으로 다른 함수 의 미리 계산 된 값으로 구성된 거대한 표 일뿐 입니다. 그렇기 때문에 구현 속도가 매우 빠릅니다. 사용하는 시리즈의 모든 계수를 계산하는 대신 빠른 조회 만 수행하므로 훨씬 빠릅니다. BTW, 그들은 사인과 코사인을 계산하기 위해 Tailor 시리즈를 사용합니다.

이게 도움이 되길 바란다.


4

sin()현재 x86 프로세서 (Intel Core 2 Duo라고 함)에서 GCC의 C 컴파일러로 컴파일 된 C 프로그램 의 경우에 대답하려고합니다 .

C 언어에서 표준 C 라이브러리가 아닌 언어 자체에 포함 된 일반적인 수학 함수, 포함 (예를 pow, sincos전력, 사인 및 코사인에 대한 각각). 헤더는 math.h에 포함되어 있습니다.

이제 GNU / Linux 시스템에서이 라이브러리 기능은 glibc (GNU libc 또는 GNU C 라이브러리)에서 제공합니다. 그러나 GCC 컴파일러 에서는 컴파일러 플래그를 사용하여 이러한 수학 함수를 사용할 수 있도록 수학 라이브러리 ( libm.so) 에 링크하려고합니다 -lm. 왜 이것이 표준 C 라이브러리의 일부가 아닌지 잘 모르겠습니다. 부동 소수점 함수의 소프트웨어 버전 또는 "소프트 플로트"입니다.

따로 : 수학 함수를 분리하는 이유는 역사적이며, 아주 오래된 유닉스 시스템에서, 아마도 내가 아는 한 공유 라이브러리를 사용할 수 있기 전에 실행 가능한 프로그램의 크기를 줄이기위한 것 입니다.

이제 컴파일러는 표준 C 라이브러리 함수 sin()(로 제공 libm.so)를 CPU / FPU의 내장 sin () 함수에 대한 기본 명령어 호출로 대체 하도록 최적화 할 수 있습니다.이 함수 는 FPU 명령어 ( FSINx86 / x87의 경우)로 존재합니다. Core 2 시리즈와 같은 최신 프로세서 (i486DX와 거의 동일). 이는 gcc 컴파일러에 전달 된 최적화 플래그에 따라 다릅니다. 컴파일러가 i386 또는 최신 프로세서에서 실행될 코드를 작성하라는 지시를 받으면 그러한 최적화를 수행하지 않습니다. -mcpu=486플래그는 이러한 최적화를 할 안전 것을 컴파일러에 알려 것입니다.

프로그램이 죄 () 함수의 소프트웨어 버전을 실행하면 이제 그것은 그래서를 기반으로 할 것 CORDIC (회전 디지털 컴퓨터 좌표) 또는 BKM 알고리즘 , 또는 일반적으로 계산하기 위해 현재 사용되는 테이블 또는 전원 시리즈 계산 가능성 초월적인 기능. [Src : http://en.wikipedia.org/wiki/Cordic#Application]

gcc의 최신 (약 2.9x 이후) 버전은 내장 된 sin __builtin_sin()버전을 제공하므로 최적화를 위해 C 라이브러리 버전에 대한 표준 호출을 대체하는 데 사용됩니다.

나는 그것이 진흙처럼 분명하다고 확신하지만, 기대했던 것보다 더 많은 정보를 제공하고 더 많은 것을 배우기 위해 많은 점을 뛰어 넘기를 바랍니다.



3

Taylor 시리즈를 사용하지 마십시오. 체비 쇼프 다항식은 위의 두 사람이 지적한 것처럼 더 빠르고 정확합니다. 구현은 다음과 같습니다 (원래 ZX Spectrum ROM) : https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
이것은 실제로 질문에 대답하지 않는 것 같습니다. 영업 이익은 삼각 함수 방법을 요구하고 있다 그들이 어떻게하지, 일반적인 C 컴파일러 / 라이브러리에 의해 계산 된 (그리고 나는 ZX 스펙트럼이 필요하지 않습니다 확신) 한다 계산. 그러나 이것은 이전 답변 중 일부에 대한 유용한 의견 일 수 있습니다 .
Ilmari Karonen

1
아 맞아. 답이 아닌 설명이어야합니다. 나는 한동안 SO를 사용하지 않았으며 시스템 작동 방식을 잊어 버렸습니다. 어쨌든 Spectrum 구현은 CPU 속도가 느리고 속도가 본질적이기 때문에 관련이 있다고 생각합니다. 그러면 최고의 알고리즘은 여전히 ​​아주 훌륭하므로 C 라이브러리가 Chebyshev 다항식을 사용하여 삼각 함수를 구현하는 것이 좋습니다.
Albert Veli

2

사인 / 코사인 / 탄젠트 계산은 실제로 Taylor 시리즈를 사용하는 코드를 통해 매우 쉽게 수행 할 수 있습니다. 직접 작성하는 데 5 초가 걸립니다.

전체 프로세스는 여기에서이 방정식으로 요약 될 수 있습니다.

죄와 비용 확장

C를 위해 작성한 몇 가지 루틴은 다음과 같습니다.

double _pow(double a, double b) {
    double c = 1;
    for (int i=0; i<b; i++)
        c *= a;
    return c;
}

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

double _sin(double x) {
    double y = x;
    double s = -1;
    for (int i=3; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _cos(double x) {
    double y = 1;
    double s = -1;
    for (int i=2; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _tan(double x) {
     return (_sin(x)/_cos(x));  
}

4
사인 및 코사인 시리즈의 연속 항에 매우 간단한 몫이 있다는 것을 사용하지 않기 때문에 이것은 다소 나쁜 구현입니다. 즉, 여기에서 O (n ^ 2)에서 O (n)으로 곱셈과 나눗셈의 수를 줄일 수 있습니다. 예를 들어 bc (POSIX multiprecision calculator) 수학 라이브러리에서와 같이 반감기 및 제곱하여 추가 축소를 수행 할 수 있습니다.
Lutz Lehmann

2
또한 요청에 따라 질문에 대답하지 않는 것 같습니다. OP는 사용자 정의 다시 구현이 아닌 일반적인 C 컴파일러 / 라이브러리에서 삼각 함수를 계산하는 방법을 묻습니다.
Ilmari Karonen

2
sin ()과 같은 "블랙 박스"기능에 대한 호기심에 대한 질문의 정신에 답하기 때문에 좋은 대답이라고 생각합니다. 여기에 최적화 된 C 소스 코드를 읽는 대신 몇 초 만에 글로 싱하여 일어나는 일을 빠르게 이해할 수있는 유일한 대답입니다.
Mike M

실제로 라이브러리는 용어가 있으면 일부 값을 곱하여 다음 용어를 얻을 수 있다는 것을 인식함으로써 훨씬 최적화 된 버전을 사용합니다. Blindy 's answer 의 예를 참조하십시오 . 당신은 거듭 제곱을 거듭하는 힘과 계승을 계산하고 있습니다
phuclv


0

Blindy의 답변에서 향상된 코드 버전

#define EPSILON .0000000000001
// this is smallest effective threshold, at least on my OS (WSL ubuntu 18)
// possibly because factorial part turns 0 at some point
// and it happens faster then series element turns 0;
// validation was made against sin() from <math.h>
double ft_sin(double x)
{
    int k = 2;
    double r = x;
    double acc = 1;
    double den = 1;
    double num = x;

//  precision drops rapidly when x is not close to 0
//  so move x to 0 as close as possible
    while (x > PI)
        x -= PI;
    while (x < -PI)
        x += PI;
    if (x > PI / 2)
        return (ft_sin(PI - x));
    if (x < -PI / 2)
        return (ft_sin(-PI - x));
//  not using fabs for performance reasons
    while (acc > EPSILON || acc < -EPSILON)
    {
        num *= -x * x;
        den *= k * (k + 1);
        acc = num / den;
        r += acc;
        k += 2;
    }
    return (r);
}

0

이것이 어떻게 수행되는지의 본질은 Gerald Wheatley의 Applied Numerical Analysis 에서 발췌 한 내용입니다 .

소프트웨어 프로그램이 컴퓨터에 여기에 이미지 설명을 입력하십시오또는 의 값을 요구할 때 여기에 이미지 설명을 입력하십시오계산할 수있는 가장 강력한 함수가 다항식 인 경우 어떻게 값을 얻을 수 있는지 궁금하십니까? 테이블에서 이것들을 보지 않고 보간합니다! 오히려 컴퓨터는 값을 매우 정확하게 제공하도록 조정 된 일부 다항식의 다항식 이외의 모든 함수에 근사합니다.

위에서 언급 할 몇 가지 요점은 일부 알고리즘은 처음 몇 번의 반복에 대해서만 실제로 테이블에서 보간한다는 것입니다. 또한 컴퓨터가 어떤 유형의 근사 다항식을 지정하지 않고 근사 다항식을 사용한다는 점을 참고하십시오. 스레드의 다른 사람들이 지적했듯이 Chebyshev 다항식은이 경우 Taylor 다항식보다 효율적입니다.


-1

당신이 원하는 경우 sin다음

 __asm__ __volatile__("fsin" : "=t"(vsin) : "0"(xrads));

당신이 원하는 경우 cos다음

 __asm__ __volatile__("fcos" : "=t"(vcos) : "0"(xrads));

당신이 원하는 경우 sqrt다음

 __asm__ __volatile__("fsqrt" : "=t"(vsqrt) : "0"(value));

기계 명령이 정확하지 않은 이유는 무엇입니까?


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.