C sin () ve diğer matematik fonksiyonlarını nasıl hesaplar?


248

Ben .NET demontajları ve GCC kaynak kodu ile gözenekli, ama gerçek uygulama sin()ve diğer matematik fonksiyonları hiçbir yerde bulamıyorum ... onlar her zaman başka bir şey referans gibi görünüyor.

Biri onları bulmama yardım edebilir mi? C'nin çalışacağı TÜM donanımların donanımdaki trig işlevlerini desteklemesi pek olası görünmüyor, bu yüzden bir yerde bir yazılım algoritması olmalı , değil mi?


Ben işlevselliği sağlayacak çeşitli şekillerde farkındayım olabilir hesaplanacak ve eğlence için Taylor serilerini kullanarak hesaplama fonksiyonları için kendi rutinleri yazdım. Üretim dillerinin ne kadar gerçek olduğunu merak ediyorum, çünkü algoritmalarımın oldukça zeki olduğunu düşünmeme rağmen tüm uygulamalarım her zaman birkaç büyüklükte daha yavaş.


2
Bu uygulamanın bağımlı olduğunu lütfen unutmayın. Hangi uygulama ile en çok ilgilendiğinizi belirtmelisiniz.
jason

3
.NET ve C'yi etiketledim çünkü her iki yere de baktım ve anlayamadım. Her ne kadar .NET sökme bakarak yönetilen C çağırıyor olabilir gibi görünüyor, ben aynı uygulama bildiğim kadarıyla.
Hank

Yanıtlar:


213

GNU libm'de uygulaması sinsisteme bağlıdır. Bu nedenle, her platform için uygulamayı sysdeps'in uygun alt dizininde bir yerde bulabilirsiniz .

Bir dizin, IBM'in katkılarıyla C'de bir uygulama içerir. Ekim 2011'den beri, bu sin()tipik bir x86-64 Linux sistemini çağırdığınızda çalışan koddur . Görünüşe göre fsinmontaj talimatından daha hızlı . Kaynak kodu: sysdeps / ieee754 / dbl-64 / s_sin.c , arayın __sin (double x).

Bu kod çok karmaşık. Hiçbir yazılım algoritması mümkün olduğunca hızlı ve aynı zamanda tüm x değerleri aralığında doğru değildir , bu nedenle kütüphane birkaç farklı algoritma uygular ve ilk işi x'e bakmak ve hangi algoritmanın kullanılacağına karar vermektir.

  • Ne zaman x çok olduğunu çok 0'a yakın, sin(x) == xdoğru cevaptır.

  • Biraz daha ileride sin(x), tanıdık Taylor serisini kullanıyor. Ancak, bu sadece 0 yakınında doğrudur, bu yüzden ...

  • Açı yaklaşık 7 ° 'den fazla olduğunda, hem sin (x) hem de cos (x) için Taylor serisi yaklaşımlarını hesaplayan, ardından yaklaşımı düzeltmek için önceden hesaplanmış bir tablodaki değerleri kullanarak farklı bir algoritma kullanılır.

  • Ne zaman | x | > 2, yukarıdaki algoritmaların hiçbiri işe yaramaz, bu nedenle kod, 0'a yakın sinveya cosbunun yerine beslenebilecek bir değer hesaplanarak başlar .

  • X'in bir NaN veya sonsuz olmasıyla ilgilenecek başka bir şube daha var .

Bu kod, daha önce hiç görmediğim bazı sayısal kesmek kullanıyor, ancak bildiğim her şey için kayan nokta uzmanları arasında iyi biliniyor olabilirler. Bazen birkaç satır kod açıklamak için birkaç paragraf alır. Örneğin, bu iki satır

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

azaltılmasında (bazen) kullanılır x , 0 olan bir değer sona farklıdır x π / 2 bir katına, özellikle göre xnx π / 2. Bunun bölünme veya dallanma olmadan yapılma şekli oldukça zekidir. Ama hiç yorum yok!


GCC / glibc'nin eski 32 bit sürümleri, fsinbazı girişler için şaşırtıcı derecede yanlış olan talimatı kullandı . Bunu sadece 2 satır kodla gösteren büyüleyici bir blog yazısı var .

fdlibm'in sinsaf C'de uygulanması glibc'lerden çok daha basittir ve iyi yorumlanmıştır. Kaynak kodu: fdlibm / s_sin.c ve fdlibm / k_sin.c


35
Bunun gerçekten x86 üzerinde çalışan kod olduğunu görmek için: çağıran bir program derleyin sin(); yazın gdb a.out, sonra break sin, sonra run, sonra disassemble.
Jason Orendorff

5
@Henry: Yine de iyi bir kod olduğunu düşünerek hata yapmayın. Gerçekten korkunç , bu şekilde kod yazmayı öğrenmeyin!
Thomas Bonini

2
@Andreas Hmm, haklısın, IBM kodu fdlibm ile karşılaştırıldığında oldukça korkunç görünüyor. Cevabı fdlibm'in sinüs rutinine bağlantılar eklemek için düzenledim.
Jason Orendorff

3
@Henry: k_sin.c'de __kernel_sintanımlanır ve saf C'dir. Tekrar tıklayın — URL'yi ilk kez tıkladım.
Jason Orendorff

3
Bağlantılı sysdeps kodu, doğru yuvarlandığından özellikle ilginçtir. Yani, görünüşe göre, sadece son zamanlarda oldukça mümkün hale gelen tüm giriş değerleri için mümkün olan en iyi cevabı veriyor. Bazı durumlarda bu yavaş olabilir, çünkü doğru yuvarlamayı sağlamak için birçok fazladan hane hesaplanması gerekebilir. Diğer durumlarda son derece hızlıdır - yeterince küçük sayılar için cevap sadece açıdır.
Bruce Dawson

67

Sinüs ve kosinüs gibi fonksiyonlar mikroişlemciler içindeki mikrokodda uygulanır. Örneğin Intel yongalarının bunlar için montaj talimatları vardır. AC derleyici bu montaj talimatlarını çağıran kod üretir. (Aksine, bir Java derleyicisi bunu yapmaz. Java, donanımdaki trig işlevlerini donanım yerine değerlendirir ve bu nedenle çok daha yavaş çalışır.)

Cips yok en azından değil tamamen açı fonksiyonları hesaplamak için Taylor serisi kullanırız. Her şeyden önce CORDIC kullanırlar , ancak CORDIC'in sonucunu parlatmak için veya çok küçük açılar için yüksek göreceli doğruluğa sahip sinüs hesaplama gibi özel durumlar için kısa bir Taylor serisi kullanabilirler. Daha fazla açıklama için bu StackOverflow yanıtına bakın .


10
sinüs ve kosinüs gibi aşkın matematik fonksiyonları mikrokodda veya mevcut 32 bit masaüstü ve sunucu işlemcilerinde donanım talimatları olarak uygulanabilir. İ486 (DX) tüm kayan nokta hesaplamaları ayrı bir yardımcı işlemci olmadan x86 serisi için yazılımda ("yumuşak şamandıra") yapılana kadar her zaman böyle değildi. Hepsi (FPU'lar) aşkın fonksiyonlar içermez (örn. Weitek 3167).
mctylr

1
Daha spesifik olabilir misiniz? Taylor serisi kullanarak bir yaklaşım nasıl "parlatılır"?
Hank

4
Bir cevabı "parlattığınız" sürece, hem sinüs hem de kosinüs hesapladığınızı varsayın. Her ikisinin de tam değerini bir noktada bildiğinizi varsayalım (örneğin CORDIC'ten) ancak değeri yakındaki bir noktada istiyorsunuz. Sonra küçük bir h farkı için, f (x + h) = f (x) + h f '(x) veya f (x + h) = f (x) + h f' (x) Taylor yaklaşımlarını uygulayabilirsiniz. + h ^ 2 f '' (x) / 2.
John D. Cook

6
x86 / x64 yongalarının sinüs (fsin) hesaplamak için bir montaj talimatı vardır, ancak bu talimat bazen oldukça yanlıştır ve bu nedenle artık nadiren kullanılmaktadır. Ayrıntılar için randomascii.wordpress.com/2014/10/09/… adresine bakın. Diğer işlemcilerin çoğunda sinüs ve kosinüs için talimatlar yoktur , çünkü bunları yazılımda hesaplamak daha fazla esneklik sağlar ve hatta daha hızlı olabilir.
Bruce Dawson

3
Intel çiplerinin içindeki kord malzemeleri genellikle KULLANILMAZ. İlk olarak, operasyonun doğruluğu ve çözünürlüğü birçok uygulama için son derece önemlidir. 7. basamağa ulaştığınızda kordic kötü bir şekilde yanlış ve tahmin edilemez. İkincisi, uygulamalarında daha da fazla soruna neden olan bir hata olduğunu duydum. Linux gcc için günah işlevine bir göz attım ve yeterince, chebyshev kullanıyor. yerleşik şeyler kullanılmaz. Oh, ayrıca, çipteki kord algoritması yazılım çözümünden daha yavaştır.
Donald Murray

63

Tamam çocuklar, profesyoneller için zaman .... Bu deneyimsiz yazılım mühendisleri ile benim en büyük şikayetleri biridir. Onlar, daha önce hiç kimse bu hesaplamaları hayatlarında yapmamış gibi, aşkın fonksiyonları sıfırdan (Taylor serisini kullanarak) hesaplamaya geliyorlar. Doğru değil. Bu iyi tanımlanmış bir sorundur ve çok akıllı yazılım ve donanım mühendisleri tarafından binlerce kez ele alınmıştır ve iyi tanımlanmış bir çözüme sahiptir. Temel olarak, aşkın işlevlerin çoğu bunları hesaplamak için Chebyshev Polinomlarını kullanır. Hangi polinomların kullanıldığı durumlara bağlıdır. Birincisi, bu konudaki incil Hart ve Cheney tarafından "Bilgisayar Yaklaşımı" adlı bir kitaptır. Bu kitapta, bir donanım toplayıcı, çarpan, bölücü vb. Olup olmadığına karar verebilir ve hangi işlemlerin en hızlı olduğuna karar verebilirsiniz. Örneğin, gerçekten hızlı bir bölücünüz varsa, sinüsü hesaplamanın en hızlı yolu P1, P2'nin Chebyshev polinomları olduğu P1 (x) / P2 (x) olabilir. Hızlı bölücü olmadan, sadece P (x) olabilir, burada P, P1 veya P2'den çok daha fazla terime sahiptir .... bu yüzden daha yavaş olurdu. Yani, ilk adım donanımınızı ve neler yapabileceğini belirlemektir. Sonra Chebyshev polinomlarının uygun kombinasyonunu seçersiniz (genellikle kosinüs için cos (ax) = aP (x) formundadır, yine P'nin bir Chebyshev polinomudur). Sonra hangi ondalık kesinlik istediğinize karar verirsiniz. Örneğin 7 basamak hassasiyet istiyorsanız, bahsettiğim kitaptaki uygun tabloya bakın ve size (hassasiyet = 7.33 için) bir N = 4 ve bir polinom numarası 3502 verir. N, polinom (yani p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0), çünkü N = 4. Sonra p4, p3, p2, p1, p2 değerleri kitabın arkasında 3502'nin altındadır (kayan noktada olacaktır). Ardından algoritmanızı yazılımda şu şekilde uygularsınız: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... ve kosinüsü 7 ondalık sayıya nasıl hesaplayacağınız budur. bu donanım üzerine yerleştirir.

Bir FPU'daki aşkın işlemlerin çoğu donanım uygulamasının genellikle bazı mikro kodları ve bunun gibi işlemleri içerdiğini unutmayın (donanıma bağlıdır). Chebyshev polinomları çoğu transandantal için kullanılır, ancak hepsi değil. Örneğin, Kare kök, önce bir arama tablosu kullanarak Newton raphson yönteminin çift yinelemesini kullanmak için daha hızlıdır. Yine, o "Bilgisayar Yaklaşımları" kitabı size bunu söyleyecektir.

Bu işlevleri uygulamayı planlıyorsanız, herkese bu kitabın bir kopyasını almasını tavsiye ederim. Bu tür algoritmalar için gerçekten incil. Kordonlar vb. Gibi bu değerleri hesaplamak için alternatif araç demetlerinin bulunduğunu unutmayın, ancak bunlar yalnızca düşük hassasiyete ihtiyaç duyduğunuz belirli algoritmalar için en iyi olma eğilimindedir. Her seferinde hassasiyeti garanti etmek için chebyshev polinomları gitmenin yoludur. Dediğim gibi, iyi tanımlanmış bir sorun. Şimdi 50 yıldır çözüldü ..... ve bu nasıl yapılır.

Şimdi, Chebyshev polinomlarının düşük dereceli bir polinom ile tek bir hassas sonuç elde etmek için kullanılabileceği teknikler vardır (yukarıdaki kosinüs örneği gibi). Daha sonra, "Gal'in Doğru Tablolar Yöntemi" gibi çok daha büyük bir polinom gitmek zorunda kalmadan doğruluğu artırmak için değerler arasında enterpolasyon yapmak için başka teknikler vardır. Bu ikinci teknik, ACM literatürüne atıfta bulunulan gönderinin ifade ettiği şeydir. Ama nihayetinde, Chebyshev Polinomları oraya giden yolun% 90'ını elde etmek için kullanılır.

Zevk almak.


6
İlk birkaç cümleyle daha fazla anlaşamadım. Ayrıca, özel işlevlerin garantili hassasiyetle hesaplanmasının zor bir sorun olduğunu hatırlamakta fayda var . Bahsettiğiniz zeki insanlar hayatlarının çoğunu bunu yaparak geçirirler. Ayrıca, daha teknik bir notta, min-max polinomları aranan graaldır ve Chebyshev polinomları onlar için daha basit vekillerdir.
Alexandre

161
Profesyonel olmayan ve başıboş (ve hafif derecede kaba) ton için -1 ve bu cevabın gereksiz gereksiz içeriğinin , başıboş ve küçümsemeden arındırılmış olması, temelde "Sık sık Chebyshev polinomlarını kullanıyorlar; bu kitaba bakın daha fazla ayrıntı için, gerçekten iyi! " Hangi, bilirsiniz, kesinlikle doğru olabilir, ama gerçekten burada SO'da istediğimiz kendi kendine yeten bir cevap değil. Böyle yoğunlaştırılmış olsa da, bu soruya iyi bir yorum yapardı.
Ilmari Karonen

2
Oyunun ilk yıllarında, genellikle hız için kritik ihtiyaç olan arama tabloları ile yapıldı). Genellikle bu şeyler için standart lib fonksiyonlarını kullanmadık.
topspin

4
Gömülü sistemlerde arama tablolarını oldukça sık kullanıyorum ve bittians (radyan yerine), ama bu özel bir uygulama için (oyunlarınız gibi). Ben adam c derleyici kayan nokta sayıları için günah nasıl hesapladığını düşünüyorum ....
Donald Murray

1
Ah, 50 yıl önce. McLaren serisiyle Burroughs B220'de böyle oynamaya başladım. Daha sonra CDC donanımı ve sonra Motorola 68000. Arcsin dağınıktı - İki polinomun bölümünü seçtim ve en uygun katsayıları bulmak için kod geliştirdim.
Rick James

15

İçin sinözel olarak Taylor açılımı kullanılarak size verecekti:

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

aralarındaki fark kabul edilebilir bir tolerans düzeyinden daha düşük olana veya yalnızca sınırlı sayıda adım (daha hızlı, ancak daha az hassas) olana kadar terimler eklemeye devam edersiniz. Bir örnek şöyle olabilir:

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;
}

Not: (1) küçük açılar için günah (x) = x yakınlığı nedeniyle çalışır. Daha büyük açılar için, kabul edilebilir sonuçlar elde etmek için gittikçe daha fazla terim hesaplamanız gerekir. While argümanını kullanabilir ve belirli bir doğruluk için devam edebilirsiniz:

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
Katsayıları biraz değiştirirseniz (ve bunları bir polinomda zor kodlarsanız), yaklaşık 2 tekrarlamayı daha erken durdurabilirsiniz.
Rick James

14

Evet, hesaplamak için yazılım algoritmaları sinda var. Temel olarak, bu tür şeylerin dijital bir bilgisayarla hesaplanması genellikle işlevi temsil eden Taylor serisine yaklaşmak gibi sayısal yöntemler kullanılarak yapılır .

Sayısal yöntemler, işlevleri keyfi bir doğruluk miktarına yaklaştırabilir ve kayan bir sayıdaki doğruluk miktarı sonlu olduğundan, bu görevlere oldukça uygundur.


12
Gerçek bir uygulama muhtemelen bir Taylor serisi kullanmayacaktır, çünkü daha etkili yollar vardır. Sadece [0 ... pi / 2] alanına doğru bir şekilde yaklaşmanız yeterlidir ve bir Taylor serisinden daha verimli bir yaklaşım sağlayacak işlevler vardır.
David Thornley

2
@David: Katılıyorum. Cevabımda "beğen" kelimesinden bahsetmek için yeterince dikkatliydim. Ancak Taylor genişlemesi, yaklaşık fonksiyonları gösteren yöntemlerin arkasındaki fikri açıklamak için basit bir yöntemdir. Bununla birlikte, Taylor serisini kullanan yazılım uygulamalarını (optimize edildiklerinden emin değilim) gördüm.
Mehrdad Afshari

1
Aslında, polinom yaklaşımları, trigonometrik fonksiyonları hesaplamanın en etkili yollarından biridir.
Jeremy Salwen

13

Taylor serisini kullanın ve serilerin terimleri arasındaki ilişkiyi bulmaya çalışın, böylece işleri tekrar tekrar hesaplamazsınız

İşte cosinus için bir örnek:

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;
}

Bunu kullanarak, zaten kullanılan olanı kullanarak toplamın yeni terimini alabiliriz (faktöriyel ve x 2p'den kaçınırız )

açıklama


2
TeX kullanarak böyle formüller oluşturmak için Google Grafik API'sını kullanabileceğinizi biliyor muydunuz? code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer

11

Bu karmaşık bir soru. X86 ailesinin Intel benzeri CPU'su sin()işlevin bir donanım uygulamasına sahiptir , ancak x87 FPU'nun bir parçasıdır ve artık 64 bit modunda (bunun yerine SSE2 kayıtlarının kullanıldığı) kullanılmamaktadır. Bu modda bir yazılım uygulaması kullanılır.

Orada böyle birkaç uygulama var. Biri fdlibm'de ve Java'da kullanılıyor. Bildiğim kadarıyla, glibc uygulaması fdlibm'in parçalarını ve IBM'in katkıda bulunduğu diğer kısımları içerir.

sin()Genellikle Taylor serisinden elde edilen polinomlar tarafından yaklaşık olarak kullanılan yaklaşımlar gibi aşkın fonksiyonların yazılım uygulamaları .


3
SSE2 kayıtları vardır değil elbette, günah moda bakılmaksızın donanım hesaplanır, (), ne x86 ne de x64 modunda hesapla günah için kullanılan ve. Hey, 2010'da yaşıyoruz :)
Igor Korkhov

7
@Igor: Hangi matematik kütüphanesine baktığınıza bağlı. X86'daki en optimize matematik kütüphanelerinin SSE yazılım uygulamalarını kullandıkları sinve cosFPU'daki donanım talimatlarından daha hızlı olduğu ortaya çıkıyor. Daha basit, daha naif kütüphaneler fsinve fcostalimatları kullanma eğilimindedir .
Stephen Canon

@Stephen Canon: Bu hızlı kütüphaneler FPU kayıtları gibi 80 bit hassasiyete sahip mi? Hassasiyetin hızını tercih ettikleri konusunda çok sinsi bir kuşkum var, ki bu elbette birçok senaryoda, örneğin oyunlarda makul. SSE ve önceden hesaplanmış ara tablolar kullanarak sinüsün 32 bit hassasiyetle hesaplanmasının FSINtam hassasiyetle kullanmaktan daha hızlı olabileceğine inanıyorum . Bana bu hızlı kütüphanelerin isimlerini söylerseniz çok minnettar olurum, bir göz atmak ilginç.
Igor Korkhov

@Igor: 64 bit modunda x86'da, en azından bildiğim tüm Unix benzeri sistemlerde, hassasiyet x87 FPU'nun 79 bitiyle değil, 64 bit ile sınırlıdır. Yazılımın uygulanması sin()hesaplanandan iki kat daha hızlıdır fsin(tam olarak daha az hassasiyetle yapıldığı için). X87'nin, açıklanan 79 bit'ten biraz daha az gerçek hassasiyete sahip olduğu biliniyor.
Thomas Pornin

1
Nitekim msvc çalışma zamanı kütüphanelerde hem 32-bit ve günahın 64-bit uygulamaları () do not FSIN talimatı kullanın. Aslında, farklı sonuçlar verirler, örneğin günah alırlar (0.70444454416678126). Bu 32-bit bir programda 0.64761068800896837 (sağ 0.5 * (eps / 2) toleranslı) ile sonuçlanacak ve 64-bit bir programda 0.64761068800896848 (yanlış) ile sonuçlanacaktır.
e.tadeu

9

Chebyshev polinomları, başka bir cevapta belirtildiği gibi, fonksiyon ve polinom arasındaki en büyük farkın olabildiğince küçük olduğu polinomlardır. Bu mükemmel bir başlangıç.

Bazı durumlarda, maksimum hata ilgilendiğiniz şey değil, maksimum göreceli hatadır. Örneğin sinüs işlevi için, x = 0 yakınındaki hata daha büyük değerlerden çok daha küçük olmalıdır; küçük bir göreceli hata istiyorsunuz . Yani günah x / x için Chebyshev polinomunu hesaplar ve bu polinomu x ile çarparsınız.

Sonra polinomun nasıl değerlendirileceğini bulmalısınız. Bunu, ara değerler küçük ve dolayısıyla yuvarlama hataları küçük olacak şekilde değerlendirmek istersiniz. Aksi takdirde, yuvarlama hataları polinomdaki hatalardan çok daha büyük olabilir. Sinüs işlevi gibi işlevlerle, dikkatsizseniz, x günahı için hesapladığınız sonucun, x <y olsa bile günah y sonucundan daha büyük olması mümkündür. Hesaplama sırasının ve yuvarlama hatası için üst sınırların hesaplanmasının dikkatli bir şekilde seçilmesi gerekir.

Örneğin, sin x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/5040 ... Saf bir şekilde günah hesaplarsanız x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - x ^ / 5040 6 ...), daha sonra parantez içinde işlev azalması olduğunu ve bu olacak x, y için bir sonraki daha büyük bir sayı ise, o zaman, bazen y sin x daha küçük olacaktır sin gerçekleşmesi. Bunun yerine, bunun gerçekleşemeyeceği günah x = x - x ^ 3 * (1/6 - x ^ 2/120 + x ^ 4/5040 ...) değerini hesaplayın.

Chebyshev polinomlarını hesaplarken, örneğin katsayıları genellikle iki kat hassasiyetle yuvarlamanız gerekir. Ancak bir Chebyshev polinomu optimal olmakla birlikte, çift hassasiyete yuvarlanmış katsayılara sahip Chebyshev polinomu, çift hassasiyet katsayılarına sahip optimal polinom değildir!

Örneğin, x, x ^ 3, x ^ 5, x ^ 7 vb. İçin katsayılara ihtiyaç duyduğunuz sin (x) için aşağıdakileri yaparsınız: sin x'in bir polinom (ax + bx ^ 3 + cx ^ 5 + dx ^ 7) ile çift kesinlikten daha yüksek, daha sonra a ile çift kesinlik arasında yuvarlayın. A ile A arasındaki fark oldukça büyük olacaktır. Şimdi bir polinom (bx ^ 3 + cx ^ 5 + dx ^ 7) ile (sin x - Ax) en iyi yaklaşımını hesaplayın. Farklı katsayılar elde edersiniz, çünkü a ve A arasındaki farka uyum sağlarlar. B'yi çift kesinlikli B'ye çevirin. Sonra polinom cx ^ 5 + dx ^ 7 vb. İle yaklaşık (sin x - Ax - Bx ^ 3). Neredeyse orijinal Chebyshev polinomu kadar iyi olan bir polinom alacaksınız, ancak Chebyshev'den çifte hassasiyete yuvarlanandan çok daha iyi olacaksınız.

Ardından, polinom seçimindeki yuvarlama hatalarını dikkate almalısınız. Polinomda yuvarlama hatasını göz ardı eden minimum hataya sahip bir polinom buldunuz, ancak polinom artı yuvarlama hatasını optimize etmek istiyorsunuz. Chebyshev polinomuna sahip olduğunuzda, yuvarlama hatası için sınırları hesaplayabilirsiniz. Diyelim ki f (x) fonksiyonunuz, P (x) polinom ve E (x) yuvarlama hatası. Optimize etmek istemiyorsunuz | f (x) - P (x) |, optimize etmek istiyorsunuz | f (x) - P (x) +/- E (x) |. Yuvarlama hatasının büyük olduğu yerde polinom hatalarını tutmaya çalışan biraz farklı bir polinom alırsınız ve yuvarlama hatasının küçük olduğu yerlerde polinom hatalarını biraz gevşetirsiniz.

Bütün bunlar size son bit'in en fazla 0.55 katı hata yuvarlama sağlar. Burada +, -, *, / son bitin en fazla 0.50 katı yuvarlama hataları vardır.


1
Bu seferki nasıl güzel bir açıklama olabilir verimli sin (x) hesaplamak, ama gerçekten C kitaplıkları / derleyiciler nasıl ortak hakkında spesifik olan OP'ın sorusuna yanıt vermiyor do it hesaplayın.
Ilmari Karonen

Chebyshev polinomları bir aralıkta maksimum mutlak değeri en aza indirir, ancak bir hedef fonksiyon ile polinom arasındaki en büyük farkı en aza indirmezler. Minimax polinomları bunu yapar.
Eric Postpischil

9

Gibi trigonometrik fonksiyon ile ilgili sin(), cos(), tan(): Yüksek kaliteli trigonometrik fonksiyonlar önemli yönünün, 5 yıl sonra, hiçbir söz olmuştur Menzil azaltma .

Bu işlevlerin herhangi birindeki ilk adım, radyan cinsinden açıyı 2 * π aralığa düşürmektir. Ancak, π mantıksızdır, çünkü x = remainder(x, 2*M_PI)hata M_PIya da makine pi gibi π değerinin yaklaştırılması gibi basit indirimler . Peki nasıl yapılır x = remainder(x, 2*π)?

İlk kütüphaneler, kaliteli sonuçlar vermek için geniş bir hassasiyet veya hazırlanmış programlama kullandı, ancak yine de sınırlı bir aralıkta double. Büyük bir değer istendiğinde sin(pow(2,30)), sonuçlar anlamsızdı 0.0ve belki de toplam doğruluk kaybı veya kısmi kesinlik kaybı gibi bir şeye ayarlanmış bir hata işareti vardı .TLOSSPLOSS

Büyük değerlerin -π ila π gibi bir aralığa iyi bir şekilde indirgenmesi sin(), kendisi gibi , temel tetikleme fonksiyonunun zorluklarına rakip olan zorlu bir sorundur .

İyi bir rapor muazzam argümanlar için argüman azaltımıdır: Sona kadar iyi (1992). İyi konuyu kapsamaktadır: gereğini tartışır ve işler çeşitli platformlarda (SPARC, PC, HP, 30+ diğer) vardı ve için kaliteli sonuçlar veren bir çözüm algoritması nasıl sağladığını tüm double gelen -DBL_MAXiçin DBL_MAX.


Orijinal bağımsız değişkenler derece cinsindeyse, ancak büyük bir değer taşıyorsa, fmod()daha fazla hassasiyet için önce kullanın . İyi bir fmod()tanıtacak hiçbir hata ve böylece mükemmel aralık azalma sağlamaktadır.

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

Çeşitli tetik kimlikleri ve remquo()daha da iyileştirme sunuyor. Örnek: sind ()


6

Kütüphane işlevlerinin gerçek uygulaması, belirli derleyici ve / veya kütüphane sağlayıcısına bağlıdır. Donanımda veya yazılımda yapılıp yapılmayacağı, bir Taylor genişletmesi olsun olmasın, vb. Değişecektir.

Bunun kesinlikle bir yardımı olmadığını anlıyorum.


5

Genellikle yazılımda uygulanır ve çoğu durumda ilgili donanım (yani montaj) çağrılarını kullanmayacaktır. Ancak, Jason'ın belirttiği gibi, bunlar uygulamaya özeldir.

Bu yazılım yordamlarının derleyici kaynaklarının bir parçası olmadığını, GNU derleyicisi için clib veya glibc gibi ilgili kitaplıkta bulunacağını unutmayın. Bkz. Http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

Daha fazla kontrol istiyorsanız, tam olarak neye ihtiyacınız olduğunu dikkatlice değerlendirmelisiniz. Tipik yöntemlerden bazıları, arama tablolarının enterpolasyonu, montaj çağrısı (genellikle yavaştır) veya kare kökler için Newton-Raphson gibi diğer yaklaşım şemalarıdır.


5

Donanımda değil, yazılımda bir uygulama istiyorsanız, bu sorunun kesin bir cevabını arayabileceğiniz yer Sayısal Tarifler Bölüm 5'tir . Benim kopyam bir kutuda, bu yüzden ayrıntıları veremem, ancak kısa sürüm (bu hakkı hatırlıyorsam), tan(theta/2)ilkel işleminiz olarak almanız ve diğerlerini oradan hesaplamanızdır. Hesaplama bir seri yaklaşımla yapılır, ancak bu çok yakınlaşan bir şeydir. bir Taylor serisinden daha hızlı bir şekilde .

Üzgünüm, elimi kitaba sokmadan daha fazla toplanamam.


5

Kaynağa vurmak ve birisinin bunu ortak kullanımda bir kütüphanede nasıl yaptığını görmek gibi bir şey yoktur; özellikle bir C kütüphanesi uygulamasına bakalım. ULibC'yi seçtim.

Günah işlevi şöyledir:

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

Bu, birkaç özel durumu ele alıyor gibi görünüyor ve ardından girdiyi [-pi / 4, pi / 4] aralığına eşlemek için bazı argüman azaltma gerçekleştiriyor (argümanı iki parçaya bölüyor, büyük bir parça ve bir kuyruk) aramadan önce

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

daha sonra bu iki parça üzerinde çalışır. Kuyruk yoksa, 13 derecelik bir polinom kullanılarak yaklaşık bir cevap oluşturulur. Kuyruk varsa, aşağıdaki prensibe dayanan küçük bir düzeltici ek alırsınız.sin(x+y) = sin(x) + sin'(x')y


4

Böyle bir işlev değerlendirildiğinde, bir düzeyde muhtemelen aşağıdakilerden biri vardır:

  • Enterpole edilen bir değerler tablosu (hızlı, yanlış uygulamalar için - örn. Bilgisayar grafikleri)
  • İstenen değere yaklaşan bir dizinin değerlendirilmesi - muhtemelen bir taylor serisi değil , büyük olasılıkla Clenshaw-Curtis gibi süslü bir dördüncülüğe dayanan bir şey.

Donanım desteği yoksa, derleyici muhtemelen ac kütüphanesini kullanmak yerine yalnızca montajcı kodunu (hata ayıklama sembolleri olmadan) yayarak ikinci yöntemi kullanır --- hata ayıklayıcınızdaki gerçek kodu izlemenizi zorlaştırır.


4

Birçok insanın işaret ettiği gibi, uygulamaya bağlıdır. Ama sorunuzu anladığım kadarıyla , matematik işlevlerinin gerçek bir yazılım uygulamasıyla ilgileniyordunuz , ancak sadece bir tane bulamadı. Bu durumda, işte buradasınız:

  • Http://ftp.gnu.org/gnu/glibc/ adresinden glibc kaynak kodunu indirin
  • Dosyada bak dosincos.cbulunan paketlenmemiş glibc kökü \ sysdeps \ ieee754 \ DBL-64 klasör
  • Benzer şekilde, matematik kütüphanesinin geri kalanının uygulamalarını bulabilirsiniz, sadece uygun ada sahip dosyayı arayın

Ayrıca, .tbluzantıya sahip dosyalara bir göz atabilirsiniz , içerikleri, ikili biçimde farklı işlevlerin önceden hesaplanmış değerlerinin büyük tablolarından başka bir şey değildir . Uygulamanın bu kadar hızlı olmasının nedeni budur: Kullandıkları dizinin tüm katsayılarını hesaplamak yerine sadece hızlı bir arama yaparlar, bu da çok daha hızlıdır. BTW, sinüs ve kosinüsü hesaplamak için Tailor serisini kullanırlar.

Umarım bu yardımcı olur.


4

sin()Geçerli bir x86 işlemcisinde GCC'nin C derleyicisi ile derlenmiş bir C programında (Intel Core 2 Duo diyelim) cevap vermeye çalışacağım .

C dilinde Temel kütüphane değil dilin kendisinde yer alacak ortak matematik fonksiyonlarını içerir (örn pow, sinve cosgüç, sinüs, kosinüs ve için sırasıyla). Başlıkları math.h dosyasına dahil edilir .

Şimdi bir GNU / Linux sisteminde, bu kütüphanelerin işlevleri glibc (GNU libc veya GNU C Kütüphanesi) tarafından sağlanır. Ancak GCC derleyicisi , bu matematik işlevlerinin kullanımını etkinleştirmek için derleyici bayrağını kullanarak matematik kitaplığına ( libm.so) bağlanmanızı ister -lm. Neden standart C kütüphanesinin bir parçası olmadığından emin değilim. Bunlar kayan nokta fonksiyonlarının bir yazılım versiyonu veya "yumuşak şamandıra" olabilir.

Bir yana: Matematik işlevlerinin ayrı olmasının nedeni tarihseldir ve bildiğim kadarıyla, muhtemelen paylaşılan kütüphaneler kullanıma sunulmadan önce, çok eski Unix sistemlerindeki yürütülebilir programların boyutunu azaltmayı amaçlıyordu .

Şimdi derleyici, CPU / FPU'nuzun bir FPU komutu ( x86 / x87 için) olarak var olan yerleşik sin () işlevine yerel bir talimat çağrısıyla değiştirilecek standart C kitaplığı işlevini sin()(tarafından sağlanan libm.so) optimize edebilir FSIN. Core 2 serisi gibi daha yeni işlemciler (bu, i486DX'e kadar doğrudur). Bu, gcc derleyicisine geçirilen optimizasyon bayraklarına bağlı olacaktır. Derleyiciye herhangi bir i386 veya daha yeni bir işlemcide yürütülecek kod yazması söylendiğinde, böyle bir optimizasyon yapmaz. -mcpu=486Bayrak böyle bir optimizasyon yapmak güvenli olduğunu derleyici bilgilendirmek istiyorum.

Program sin () fonksiyonunun yazılım sürümünü idam Şimdi, eğer o kadar bir dayalı yapardı CORDIC (Rotasyon sayısal bilgisayar koordinat) ya da BKM algoritması veya daha yaygın olarak hesaplanması şimdi kullanılan bir tablo veya güç serisi hesaplama olasılıkla böyle aşkın işlevler. [Src: http://en.wikipedia.org/wiki/Cordic#Application]

Gcc'nin son zamanlarda (yaklaşık 2.9x'ten beri) sürümü, __builtin_sin()bir optimizasyon olarak C kitaplığı sürümüne yapılan standart çağrıyı değiştirmek için kullanacağı yerleşik bir sin sürümü sunar .

Eminim ki bu çamur kadar açıktır, ancak umarım size beklediğinizden daha fazla bilgi verir ve daha fazla bilgi edinmek için puanlardan atlarsınız.



3

Taylor serisini kullanmayın. Chebyshev polinomları, yukarıdaki birkaç kişinin belirttiği gibi, hem daha hızlı hem de daha doğrudur. İşte bir uygulama (aslen ZX Spectrum ROM'dan): https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
Bu soruyu sorulduğu gibi cevaplamıyor gibi görünüyor. OP trigonometri fonksiyonları nasıl soruyor edilir onlar nasıl, ortak C derleyicileri / kütüphaneler tarafından hesaplanan (ve ZX Spectrum hak değildir eminim) olmalıdır hesaplanabilir. Yine de bu, önceki yanıtların bazıları için yararlı bir yorum olabilirdi .
Ilmari Karonen

1
Ah, haklısın. Bir cevap değil, bir yorum olmalı. Bir süredir SO kullanmadım ve sistemin nasıl çalıştığını unuttum. Her neyse, Spectrum uygulamasının alakalı olduğunu düşünüyorum çünkü gerçekten yavaş bir CPU'ya sahipti ve hız çok önemliydi. O zaman en iyi algoritma elbette hala oldukça iyidir, bu yüzden C kütüphanelerinin Chebyshev polinomlarını kullanarak trig fonksiyonları uygulaması iyi bir fikir olacaktır.
Albert Veli

2

Taylor serisini kullanarak sinüs / kosinüs / tanjant hesaplamak aslında çok kolaydır. Birini yazmak 5 saniye kadar sürer.

Tüm süreç buradaki bu denklemle özetlenebilir:

günah ve maliyet genişlemesi

İşte C için yazdığım bazı rutinler:

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
Sinüs ve kosinüs serisinin birbirini izleyen terimlerinin çok basit bölümleri olmadığı için bu oldukça kötü bir uygulamadır. Bu, çarpma ve bölme sayısını burada O (n ^ 2) 'den O (n)' ye azaltabileceği anlamına gelir. Örneğin bc (POSIX çoklu hassasiyetli hesap makinesi) matematik kütüphanesinde yapıldığı gibi yarıya indirilerek ve kare haline getirilerek daha fazla azaltma elde edilir.
Lutz Lehmann

2
Ayrıca soruyu sorulduğu gibi cevaplıyor gibi görünmüyor; OP, özel yeniden uygulamalar için değil, trig fonksiyonlarının ortak C derleyicileri / kütüphaneleri tarafından nasıl hesaplandığını soruyor.
Ilmari Karonen

2
Günah () gibi başka bir "kara kutu" işlevi hakkındaki merakın (ve sadece tahmin edebileceğim) sorunun ruhuna cevap verdiği için iyi bir cevap olduğunu düşünüyorum. Buradaki tek cevap, optimize edilmiş C kaynak kodunu okumak yerine, birkaç saniye içinde üzerine gelerek neler olduğunu hızlı bir şekilde anlama şansı veren tek cevaptır.
Mike M

aslında kütüphaneler çok daha optimize edilmiş sürümü kullanır, bir terim olduğunda, bir sonraki terimi bazı değerleri çarparak elde edebileceğinizi fark ederek. Blindy'nin cevabındaki bir örneğe bakın . Güç ve faktöriyelleri tekrar tekrar
hesaplıyorsunuz


0

Blindy'nin cevabından geliştirilmiş kod sürümü

#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

Bunun nasıl yapıldığının özü, Gerald Wheatley'nin Uygulamalı Sayısal Analiz'in bu alıntısında yatmaktadır :

Yazılım programı bir değer elde etmek için bilgisayar sorduğunda resim açıklamasını buraya girinya resim açıklamasını buraya girin, bunu hesaplayabilir en güçlü işlevler polinomları ise o değerler elde edebilirsiniz merak var? Bu tablolara bakmak ve enterpolat yok! Bunun yerine, bilgisayar, değerleri çok doğru bir şekilde vermek için uyarlanmış bazı polinomlardan polinomlar dışındaki her işleve yaklaşır.

Yukarıda değinilmesi gereken birkaç nokta, bazı algoritmaların yalnızca ilk birkaç yineleme için de olsa bir tablodan enterpolat yapmasıdır. Ayrıca, bilgisayarların hangi tür yaklaşık polinomu belirtmeden yaklaşık polinomları kullandığından bahsettiğine dikkat edin. İplikteki diğerlerinin belirttiği gibi, Chebyshev polinomları bu durumda Taylor polinomlarından daha etkilidir.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.