Cはsin()やその他の数学関数をどのように計算しますか?


248

私は.NET逆アセンブリとGCCソースコードを調べてきましたが、実際の実装sin()や他の数学関数をどこにも見つけることができません...それらは常に何か他のものを参照しているようです。

誰かが私を見つけてくれますか?Cを実行するすべてのハードウェアがハードウェアのトリガー関数をサポートすることはありそうにないので、どこかにソフトウェアアルゴリズムがあるはずですよね?


私は関数計算できるいくつかの方法を知っており、楽しみのためにテイラー級数を使用して関数を計算するための独自のルーチンを作成しました。私のアルゴリズムはかなり賢いと思いますが(明らかにそうではありません)、私の実装のすべてが常に数桁遅いので、実際のプロダクション言語がどのようにそれを行うかについて知りたいです。


2
この実装に依存することに注意してください。あなたは、ほとんどのことに興味があるどの実装を指定する必要があります。
ジェイソン・

3
.NETとCのタグを付けたのは、両方の場所を調べたがどちらも理解できなかったためです。。
2010

回答:


213

GNU libmでは、の実装sinはシステムに依存しています。したがって、各プラットフォームの実装は、sysdepsの適切なサブディレクトリのどこかにあります。

1つのディレクトリーには、IBM提供のCでの実装が含まれています。2011年10月以降、これはsin()典型的なx86-64 Linuxシステムを呼び出したときに実際に実行されるコードです。どうやら、fsinアセンブリ命令よりも高速です。ソースコード:sysdeps / ieee754 / dbl-64 / s_sin.c、を探します__sin (double x)

このコードは非常に複雑です。1つのソフトウェアアルゴリズムが可能な限り高速で、x値の範囲全体で正確でもないため、ライブラリはいくつかの異なるアルゴリズムを実装します。その最初の仕事は、xを調べて使用するアルゴリズムを決定することです。

  • xが0 に非常近い場合sin(x) == xは、正解です。

  • 少し先にsin(x)、おなじみのテイラーシリーズを使用します。ただし、これは0付近でのみ正確であるため、...

  • 角度が約7°を超えると、別のアルゴリズムが使用され、sin(x)とcos(x)の両方のテイラー級数近似が計算されます。次に、事前計算されたテーブルの値を使用して近似を調整します。

  • いつ| x | > 2、上記のアルゴリズムはどれも機能しないため、コードは、0に近い、sinまたはcosその代わりにます。

  • xがNaNまたは無限大であることを処理する別のブランチがあります。

このコードは、これまで見たことのない数値ハックを使用していますが、浮動小数点の専門家の間ではよく知られているかもしれません。時には、数行のコードで説明するのに数段落かかることがあります。たとえば、次の2行

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

還元に(時々 )に使用されるXを 0に値近くまでとは異なるX π/ 2の倍数で、具体的にはxn×π/ 2。分割や分岐なしでこれを行う方法は、かなり賢いです。しかし、コメントは一切ありません!


GCC / glibcの古い32ビットバージョンでは、 fsin命令をが、これは一部の入力では驚くほど不正確です。たった2行のコードでこれを説明する魅力的なブログ投稿があります。

sin純粋なC でのfdlibmの実装は、glibc の実装よりもはるかに単純で、コメントが適切です。ソースコード:fdlibm / s_sin.cおよびfdlibm / k_sin.c


35
これが実際にx86で実行されるコードであることを確認するには、次を呼び出すプログラムをコンパイルしますsin()。と入力しgdb a.out、次にbreak sin、次にrun、と入力しdisassembleます。
Jason Orendorff、2010

5
@ヘンリー:良いコードだと思って間違えないでください。それは本当にひどいです、そのようにコーディングすることを学ばないでください!
Thomas Bonini、2010

2
@Andreasうーん、そうです、IBMコードはfdlibmに比べてかなりひどく見えます。回答を編集してfdlibmのサインルーチンへのリンクを追加しました。
Jason Orendorff、

3
@Henry:k_sin.c __kernel_sinで定義されていますが、これは純粋なCです。もう一度クリックします。最初にURLを誤って入力しました。
Jason Orendorff、2010

3
リンクされたsysdepsコードは、正しく丸められるため、特に興味深いものです。つまり、すべての入力値に対して可能な限り最良の答えが得られるようですが、これはごく最近可能になったものです。場合によっては、正しい丸めを保証するために多くの桁を計算する必要があるため、これが遅くなることがあります。他の場合では、それは非常に高速です-十分に小さい数の場合、答えは角度です。
Bruce Dawson、

66

サインやコサインなどの機能は、マイクロプロセッサ内のマイクロコードに実装されています。たとえば、Intelチップにはこれらの組み立て手順があります。Cコンパイラは、これらのアセンブリ命令を呼び出すコードを生成します。(対照的に、Javaコンパイラーは動作しません。Javaはハードウェアではなくソフトウェアでトリガー関数を評価するため、実行速度が大幅に低下します。)

チップ、少なくとも完全にではなく、トリガー関数を計算するためにテイラー級数を使用しません。最初にCORDICを使用しますが、CORDICの結果を洗練するために、または非常に小さな角度で高い相対精度で正弦を計算するなどの特別な場合に、短いTaylorシリーズを使用することもできます。詳細については、このStackOverflowの回答をご覧ください。


10
サインやコサインのような超越的な数学関数は、マイクロコードで、または現在の32ビットデスクトップおよびサーバープロセッサのハードウェア命令として実装できます。i486(DX)のすべての浮動小数点計算が、個別のコプロセッサーなしでx86シリーズのソフトウェア( "soft-float")で実行されるまで、これは常に当てはまるわけではありませんでした。すべて(FPU)に超越関数が含まれているわけではありません(例:Weitek 3167)。
mctylr 2010

1
より具体的にできますか?テイラー級数を使用して近似をどのように「磨き」ますか?
ハンク

4
答えを「磨く」ことに関して、サインとコサインの両方を計算しているとしましょう。1つの点(CORDICなど)で両方の正確な値はわかっているが、近くの点の値が必要だとします。次に、小さな差hに対して、テイラー近似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
Intelチップの内部にあるコーディックなものは、通常は使用されません。まず、操作の精度と分解能は多くのアプリケーションにとって非常に重要です。Cordicは、7桁目付近になると不正確であり、予測不可能であることで有名です。次に、実装にバグがあり、さらに多くの問題が発生するとのことです。私はlinux gccのsin関数を調べましたが、確かに、それはchebyshevを使用しています。組み込みのものは使用されません。また、チップ内のコーディックアルゴリズムはソフトウェアソリューションよりも低速です。
Donald Murray、

63

OKキディ、プロの時間...これは、経験の浅いソフトウェアエンジニアに対する私の最大の不満の1つです。彼らは、超越関数をゼロから計算します(テイラーのシリーズを使用)。まるで、誰もこれまでにこれらの計算を行ったことがないかのように。違います。これは明確に定義された問題であり、非常に巧妙なソフトウェアおよびハードウェアエンジニアによって何千回もアプローチされてきており、明確に定義されたソリューションがあります。基本的に、超越関数のほとんどは、チェビシェフ多項式を使用して計算します。使用される多項式に関しては、状況によって異なります。まず、この問題に関する聖書は、ハートとチェイニーによる「Computer Approximations」と呼ばれる本です。その本では、ハードウェアの加算器、乗算器、除算器などがあるかどうかを判断し、どの演算が最も高速かを判断できます。たとえば、本当に速いディバイダーがあったら、サインを計算する最も速い方法は、P1(x)/ P2(x)です。ここで、P1、P2はチェビシェフ多項式です。高速除算器がない場合、それは単にP(x)になる可能性があります。ここで、PはP1またはP2よりもはるかに多くの項を持っています。したがって、最初のステップは、ハードウェアとそれができることを決定することです。次に、チェビシェフ多項式の適切な組み合わせを選択します(通常、コサインの場合はcos(ax)= aP(x)の形式で、ここでもPはチェビシェフ多項式です)。次に、必要な小数精度を決定します。たとえば、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 ....これは、コサインを7桁の10進数に計算する方法ですそのハードウェア上の場所。

FPUでの超越演算のほとんどのハードウェア実装には、通常、いくつかのマイクロコードとこのような演算が含まれます(ハードウェアによって異なります)。チェビシェフ多項式は、すべてではなく、ほとんどの超越関数に使用されます。たとえば、平方根は、ルックアップテーブルを最初に使用して、ニュートンラフソン法の二重反復を使用する方が高速です。繰り返しますが、その本「コンピュータの近似」はあなたにそれを教えてくれます。

これらの機能を実装する予定がある場合は、その本のコピーを入手することをどなたにもお勧めします。それは本当にこれらの種類のアルゴリズムの聖書です。コーディックなどのこれらの値を計算するための代替手段がたくさんあることに注意してください。これらは、低い精度しか必要としない特定のアルゴリズムに最適な傾向があります。毎回精度を保証するには、チェビシェフ多項式が適しています。私が言ったように、明確に定義された問題。50年前から解決されています.....そしてそれがどのように行われたかです。

さて、言われているように、チェビシェフ多項式を使用して、低次の多項式で単精度の結果を得ることができるテクニックがあります(上記のコサインの例のように)。次に、「Galの正確なテーブル法」など、より大きな多項式に行かなくても値を補間して精度を上げる他の手法があります。この後者の手法は、ACM文献を参照する投稿が参照しているものです。しかし、最終的には、チェビシェフ多項式がそこに到達するための90%を達成するために使用されるものです。

楽しい。


6
最初の数文にはこれ以上同意できませんでした。また、保証された精度で特殊関数を計算することは難しい問題であることを思い出してください。あなたが言及する賢い人々は、人生のほとんどをこれをやっています。また、より技術的なメモでは、min-max多項式は求められているグラールであり、チェビシェフ多項式はそれらのより単純なプロキシです。
Alexandre C.

161
非専門的でとりとめのない(そして穏やかに失礼な)トーンの場合、およびこの答えの実際の非冗長なコンテンツは、とりとめのない言い訳を取り除いたため、基本的には「彼らはしばしばチェビシェフ多項式を使用します。この本を参照してください。詳細については、それは本当に良いです!」どちらが正しいかはよくわかりますが、SOでここで求めているような自己完結型の答えではありません。そのように凝縮されていれば、それでも質問に対してまともなコメントをすることになるでしょう。
Ilmari Karonen、2015

2
ゲーム開発の初期の頃には、通常、速度の決定的な必要性があるルックアップテーブルを使用して行われていました。通常、これらの標準のlib関数は使用しませんでした。
2016年

4
私は組み込みシステムで(ラジアンの代わりに)非常に頻繁にルックアップテーブルを使用していますが、これは(ゲームなどの)特殊なアプリケーション用です。私は男がcコンパイラが浮動小数点数の罪をどのように計算するかに興味があると思います...
Donald Murray

1
ああ、50年前。私はマクラーレンシリーズのバローズB220でそのようなもので遊んだ。その後のCDCハードウェア、次にMotorola68000。Arcsinは乱雑でした。2つの多項式の商を選び、最適な係数を見つけるためにコードを開発しました。
リック・ジェームズ

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回の反復を早く停止できます。
リック・ジェームズ

14

はい、計算のためのソフトウェアアルゴリズムsinもあります。基本的に、これらの種類のものをデジタルコンピューターで計算するには、通常、テイラー級数を近似するような数値的方法を使用します、関数を表すます。

数値メソッドは、関数を任意の精度で近似することができます。浮動小数点数の精度は有限であるため、これらのタスクに非常に適しています。


12
より効率的な方法があるため、実際の実装ではおそらくTaylorシリーズを使用しません。ドメイン[0 ... pi / 2]で正しく近似するだけでよく、Taylor級数よりも効率的に適切な近似を提供する関数があります。
David Thornley、2010

2
@David:同意する。私は私の答えの中で「好き」という言葉に言及するのに十分注意しました。しかし、テイラー展開は、関数を近似するメソッドの背後にある考え方を説明する簡単なものです。とはいえ、テイラーシリーズを使用したソフトウェア実装(最適化されているかどうかはわかりません)を見てきました。
Mehrdad Afshari、2010

1
実際、多項式近似は三角関数を計算する最も効率的な方法の1つです。
Jeremy Salwen、2011年

13

テイラー級数を使用し、級数間の関係を見つけて、何度も計算しないようにします。

これは余弦の例です:

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
Google Chart APIを使用して、TeXを使用してこのような数式を作成できることをご存知ですか?code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer

11

それは複雑な問題です。x86ファミリのIntelライクなCPUには、sin()関数のハードウェア実装がありますが、x87 FPUの一部であり、64ビットモードでは使用されません(代わりにSSE2レジスタが使用されます)。そのモードでは、ソフトウェア実装が使用されます。

そこにいくつかのそのような実装があります。1つはfdlibmにあり、Javaで使用されます。私の知る限り、glibcの実装にはfdlibmの一部と、IBMが提供するその他の部分が含まれています。

超越関数のソフトウェア実装は、sin()通常、多項式による近似を使用します。これは、しばしばテイラー級数から取得されます。


3
SSE2レジスタは、x86モードでもx64モードでも、sin()の計算に使用されません。もちろん、sinは、モードに関係なくハードウェアで計算されます。ねえ、それは私たちが住んでいる2010年です:)
イゴールコルホフ

7
@Igor:それはあなたが見ている数学ライブラリに依存します。それはのためのx86使用SSEソフトウェアの実装で最も最適化された数学ライブラリことが判明sinし、cosより高速なFPUのハードウェア命令よりもあります。より単純で素朴なライブラリは、fsinand fcos命令を使用する傾向があります。
スティーブンキャノン

@Stephen Canon:これらの高速ライブラリは、FPUレジスタと同様に80ビット精度ですか?私は、精度よりも速度を優先しているという非常に卑劣な疑いを持っています。もちろん、これは、ゲームなどの多くのシナリオでは妥当です。また、SSEと事前計算された中間テーブルを使用して32ビット精度で正弦を計算する方がFSIN、完全精度で使用するよりも高速であると私は信じています。これらの高速ライブラリの名前を教えていただければありがたく思います。
Igor Korkhov 2010

@Igor:64ビットモードのx86、少なくとも私が知っているすべてのUnixライクなシステムでは、精度は64ビットに制限されており、x87 FPUの79ビットには制限されていません。のソフトウェアの実装は、計算sin()よりも約2倍速くなっていますfsin(正確には、精度が低いため)。x87は、発表された79ビットよりも実際の精度が少し低いことが知られていることに注意してください。
トーマスポルノ

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のチェビシェフ多項式を計算し、その多項式にxを乗算します。

次に、多項式を評価する方法を理解する必要があります。中間値が小さく、丸め誤差が小さくなるように評価したいとします。そうしないと、丸め誤差が多項式の誤差よりもはるかに大きくなる可能性があります。また、正弦関数などの関数では、不注意である場合、x <yであっても、sin xに対して計算した結果が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 ...)を計算してください。

たとえば、チェビシェフ多項式を計算するときは、通常、係数を倍精度に丸める必要があります。しかし、チェビシェフ多項式は最適ですが、倍精度に丸められた係数を持つチェビシェフ多項式は、倍精度係数を持つ最適多項式ではありません!

たとえば、sin(x)の場合、x、x ^ 3、x ^ 5、x ^ 7などの係数が必要です。次のようにします。多項式(ax + bx ^ 3 + cx ^ 5 + dx ^ 7)を倍精度より高くしてから、aを倍精度に丸め、Aを与えます。aとAの差は非常に大きくなります。次に、(sin x-Ax)の最良の近似を多項式(bx ^ 3 + cx ^ 5 + dx ^ 7)で計算します。aとAの差に適応するため、異なる係数が得られます。bを倍精度Bに丸めます。次に、(sin x-Ax-Bx ^ 3)を多項式cx ^ 5 + dx ^ 7で近似します。元のチェビシェフ多項式とほぼ同じ多項式が得られますが、倍精度に丸められたチェビシェフよりもはるかに優れています。

次に、多項式の選択で丸め誤差を考慮する必要があります。丸め誤差を無視した多項式で誤差が最小の多項式を見つけましたが、多項式と丸め誤差を最適化したいと考えています。チェビシェフ多項式を取得したら、丸め誤差の範囲を計算できます。たとえば、f(x)は関数、P(x)は多項式、E(x)は丸め誤差です。最適化したくない| f(x)-P(x)|、最適化したい| f(x)-P(x)+/- E(x)|。丸め誤差が大きい場合は多項式誤差を抑え、丸め誤差が小さい場合は多項式誤差を少し緩和するわずかに異なる多項式が得られます。

これにより、最後のビットの最大0.55倍の丸め誤差が簡単に得られます。ここで、+、-、*、/は、最後のビットの最大0.50倍の丸め誤差があります。


1
これは、1つは、どのように素敵な説明であることが効率的罪(X)を計算するが、本当に、特に一般的なCライブラリ/コンパイラがどのようにについてですOPの質問に答えるようには見えないそれを計算します。
Ilmari Karonen

チェビシェフ多項式は、区間の最大絶対値を最小化しますが、ターゲット関数と多項式の間の最大の差を最小化しません。ミニマックス多項式はそれを行います。
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()、自体のような基本的なトリガー関数の課題に匹敵する挑戦的な問題です。

優れたレポートは、巨大な引数に対する引数の削減です。問題を適切にカバーします。さまざまなプラットフォーム(SPARC、PC、HP、30以上)での必要性と状況について説明し、からまでのすべてに 高品質の結果をもたらすソリューションアルゴリズムを提供します。double-DBL_MAXDBL_MAX


元の引数が度単位であり、値が大きい可能性がある場合は、fmod()精度を上げるために最初に使用します。良いものfmod()エラーを引き起こさないので、優れた範囲縮小を提供します。

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

さまざまなトリガーIDがあり、remquo()さらに改善されています。サンプル:sind()


6

ライブラリ関数の実際の実装は、特定のコンパイラやライブラリプロバイダ、またはその両方です。ハードウェアで行われるかソフトウェアで行われるか、テイラー展開であるかどうかなどは異なります。

私はそれが絶対に役に立たないことを理解しています。


5

通常、これらはソフトウェアで実装されており、ほとんどの場合、対応するハードウェア(つまり、アセンブリ)呼び出しを使用しません。ただし、Jasonが指摘したように、これらは実装固有のものです。

これらのソフトウェアルーチンはコンパイラソースの一部ではなく、clibやGNUコンパイラのglibcなどの対応するライブラリにあります。http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functionsを参照してください

より詳細な制御が必要な場合は、必要なものを正確に慎重に評価する必要があります。典型的な方法のいくつかは、ルックアップテーブルの補間、アセンブリ呼び出し(多くの場合遅い)、または平方根のNewton-Raphsonなどの他の近似スキームです。


5

ハードウェアではなくソフトウェアでの実装が必要な場合は、この質問に対する明確な回答を探す場所は、数値レシピの第5章です。私のコピーは箱に入っているので詳細を述べることはできませんが、短いバージョン(私がこの権利を覚えている場合)はtan(theta/2)、プリミティブな操作として他の操作を計算することです。計算は級数近似で行われますが、これはテイラー級数よりもはるかに速く収束します。

申し訳ありませんが、本を手に入れずにこれ以上思い出すことはできません。


5

ソースにアクセスして、一般的に使用されているライブラリーで実際に誰かがそれをどのように実行したかを確認することに他なりません。特に1つのCライブラリの実装を見てみましょう。私はuLibCを選びました。

sin関数は次のとおりです。

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

これは、いくつかの特殊なケースを処理し、入力を範囲[-pi / 4、pi / 4]にマップするために引数の削減を実行するように見えます(引数を2つの部分、大きな部分と末尾に分割します)。呼び出す前に

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

次に、これらの2つの部分で動作します。テールがない場合は、次数13の多項式を使用して近似解が生成されます。テールがある場合は、次の原則に基づいて小さな修正が追加されます。sin(x+y) = sin(x) + sin'(x')y


4

そのような関数が評価されるときはいつでも、あるレベルではおそらく次のいずれかが存在します:

  • 補間された値のテーブル(高速で不正確なアプリケーション-コンピュータグラフィックスなど)
  • 目的の値に収束するシリーズの評価---おそらくテイラーシリーズではなく、おそらくクレンショウカーティスのような派手な求積法に基づくものです。

ハードウェアサポートがない場合、コンパイラはおそらく後者の方法を使用し、acライブラリを使用するのではなく、アセンブラコードのみ(デバッグシンボルなし)を発行します---デバッガーで実際のコードを追跡するのは難しいです。


4

多くの人々が指摘したように、それは実装に依存しています。しかし、私があなたの質問を理解している限り、数学関数の実際のソフトウェア実装に興味を持っていましたが、それを見つけることができませんでした。これが事実である場合、あなたはここにいます:

  • http://ftp.gnu.org/gnu/glibc/からglibcソースコードをダウンロードします。
  • 解凍されたglibcルート \ sysdeps \ ieee754 \ dbl-64フォルダーにdosincos.cあるファイルを確認します
  • 同様に、数学ライブラリの残りの実装を見つけることができます。適切な名前のファイルを探してください

.tbl拡張子が付いたファイルを確認することもできます。その内容は、バイナリ形式のさまざまな関数の事前計算された値の巨大なテーブルにすぎません。そのため、実装は非常に高速です。使用する系列のすべての係数を計算する代わりに、クイックルックアップを実行するだけで、はるかに高速です。ところで、彼らはTailorシリーズを使ってサインとコサインを計算しています。

これがお役に立てば幸いです。


4

sin()現在のx86プロセッサー(Intel Core 2 Duoとしましょう)でGCCのCコンパイラーを使用してコンパイルされたCプログラムの場合について、お答えします。

C言語で標準Cライブラリ(例えば、言語自体に含まれていない、一般的な数学関数を含みpowsinそしてcos、電源のための正弦、それぞれ余弦)。ヘッダーはmath.hに含まれています。

現在GNU / Linuxシステムでは、これらのライブラリ関数はglibc(GNU libcまたはGNU Cライブラリ)によって提供されています。ただし、GCCコンパイラは、コンパイラフラグを使用して数学ライブラリlibm.so)にリンクし、-lmこれらの数学関数の使用を有効にすることを求めています。標準Cライブラリに含まれていない理由がわかりません。これらは、浮動小数点関数のソフトウェアバージョン、つまり「ソフトフロート」になります。

余談:数学関数を分離する理由は歴史的であり、私が知る限り、おそらく共有ライブラリが利用可能になる前に、非常に古いUnixシステムで実行可能プログラムのサイズを削減することのみを目的としていました。

これでコンパイラは、標準のCライブラリ関数sin()(によって提供されるlibm.so)を最適化して、CPU / FPUの組み込みsin()関数へのネイティブ命令の呼び出しに置き換えることができます。これは、FPU命令(FSINx86 / x87用)としてCore 2シリーズのような新しいプロセッサー(これはi486DXまでさかのぼります)。これは、gccコンパイラーに渡される最適化フラグに依存します。コンパイラーがi386以降のプロセッサーで実行されるコードを作成するように指示された場合、そのような最適化は行われません。-mcpu=486フラグは、このような最適化を行うために安全であることをコンパイラに通知します。

プログラムがsin()関数のソフトウェアバージョンを実行すると、CORDICに基づいて実行されます(回転デジタルコンピュータを座標)またはBKMアルゴリズム、またはより多くの可能性が一般的に計算するのに使用されるようになりましたテーブルやパワーシリーズの計算そのような超越関数。[ソース: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
これは、質問されたとおりの質問には実際には答えていないようです。OPはtrigの機能がどのように求めている彼らはどのように、一般的なCコンパイラ/ライブラリで計算(と私はZXスペクトラムは資格がないかなり確信している)ではないはずです計算します。ただし、これは以前の回答の一部については役立つコメントだったかもしれません。
Ilmari Karonen

1
ああ、そうです。それはコメントであるべきで、答えではなかったはずです。私はしばらくSOを使用しておらず、システムの動作を忘れていました。とにかく、CPUが非常に遅く、速度が本質的だったので、Spectrumの実装は適切だと思います。そのとき、最良のアルゴリズムは確かにまだかなり良いので、Cライブラリがチェビシェフ多項式を使用してトリガー関数を実装するのは良い考えです。
Albert Veli

2

サイン/コサイン/タンジェントの計算は、テイラー級数を使用するコードで実際に行うのが非常に簡単です。自分で書くには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倍精度計算機)数学ライブラリーで行われるように、2分の1と2乗によってさらに削減されます。
Lutz Lehmann 2014

2
また、質問どおりに質問に回答していないようです。OPは、カスタムの再実装ではなく、一般的なCコンパイラ/ライブラリによってTrig関数がどのように計算されるかを尋ねています。
Ilmari Karonen

2
sin()のような「ブラックボックス」関数についての好奇心(そしてもちろん私は推測しかできません)の精神に答えるので、それは良い答えだと思います。これは、最適化されたCソースコードを読み取るのではなく、数秒でそれを理解することによって何が起こっているのかをすばやく理解する機会を与える唯一の答えです。
Mike M

実際、ライブラリははるかに最適化されたバージョンを使用します。用語を取得すると、いくつかの値を乗算することで次の用語を取得できることを実現します。ブリンディーの回答の例をご覧ください。あなたは力と階乗を何度も何度も計算していますが、これははるかに遅いです
phuclv


0

ブリンディの答えからのコードの改良版

#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からのこの抜粋にあります。

ソフトウェアプログラムがコンピュータにここに画像の説明を入力してくださいまたはの値を取得するように要求したときに、 ここに画像の説明を入力してください計算できる最も強力な関数が多項式である場合、どのようにして値を取得できるのか疑問に思いましたか?テーブルでこれらを調べて補間しません!むしろ、コンピュータは、値を非常に正確に与えるように調整された多項式から、多項式以外のすべての関数を近似します。

上記で言及すべきいくつかのポイントは、最初の数回の反復のみであるにもかかわらず、一部のアルゴリズムはテーブルから完全に補間することです。また、コンピュータがどのタイプの近似多項式を指定することなく、近似多項式を利用することについても言及しています。スレッドの他の人が指摘したように、この場合、チェビシェフ多項式はテイラー多項式よりも効率的です。


-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.