In che modo C calcola sin () e altre funzioni matematiche?


248

Ho analizzato i disassemblaggi .NET e il codice sorgente GCC, ma non riesco a trovare da nessuna parte l'implementazione effettiva di sin()e altre funzioni matematiche ... sembrano sempre fare riferimento a qualcos'altro.

Qualcuno può aiutarmi a trovarli? Sento che è improbabile che TUTTO l'hardware su cui C funzionerà supporti le funzioni di trigger nell'hardware, quindi ci deve essere un algoritmo software da qualche parte , giusto?


Sono a conoscenza di diversi modi in cui le funzioni possono essere calcolate e ho scritto le mie routine per calcolare le funzioni usando le serie di Taylor per divertimento. Sono curioso di sapere quanto siano reali i linguaggi di produzione, dal momento che tutte le mie implementazioni sono sempre più lente di diversi ordini di grandezza, anche se penso che i miei algoritmi siano piuttosto intelligenti (ovviamente non lo sono).


2
Si noti che questa implementazione dipende. Devi specificare quale implementazione ti interessa di più.
Jason,

3
Ho taggato .NET e C perché ho cercato in entrambi i posti e non sono riuscito a capire neanche. Anche se guardando lo smontaggio di .NET sembra che potrebbe essere chiamato in C non gestito, quindi per quanto ne so hanno la stessa implementazione.
Hank

Risposte:


213

In GNU libm, l'implementazione di sindipende dal sistema. Pertanto è possibile trovare l'implementazione, per ciascuna piattaforma, da qualche parte nella sottodirectory appropriata di sysdeps .

Una directory include un'implementazione in C, fornita da IBM. Da ottobre 2011, questo è il codice che viene effettivamente eseguito quando si chiama sin()un tipico sistema Linux x86-64. Apparentemente è più veloce delle fsinistruzioni di montaggio. Codice sorgente: sysdeps / ieee754 / dbl-64 / s_sin.c , cercare __sin (double x).

Questo codice è molto complesso. Nessun algoritmo software è il più veloce possibile e preciso su tutta la gamma di valori x , quindi la libreria implementa diversi algoritmi diversi e il suo primo compito è guardare x e decidere quale algoritmo usare.

  • Quando x è molto molto vicino a 0, sin(x) == xè la risposta giusta.

  • Un po 'più in là, sin(x)usa la familiare serie Taylor. Tuttavia, questo è accurato solo vicino a 0, quindi ...

  • Quando l'angolo è superiore a circa 7 °, viene utilizzato un algoritmo diverso, che calcola le approssimazioni della serie Taylor sia per sin (x) che per cos (x), quindi utilizza i valori di una tabella pre-calcolata per perfezionare l'approssimazione.

  • Quando | x | > 2, nessuno degli algoritmi sopra funzionerebbe, quindi il codice inizia calcolando un valore più vicino a 0 che può essere alimentato sino cosinvece.

  • C'è ancora un altro ramo da affrontare con x essere una NaN o infinito.

Questo codice utilizza alcuni hack numerici che non ho mai visto prima, anche se per quello che ne so potrebbero essere ben noti tra gli esperti in virgola mobile. A volte alcune righe di codice richiederebbero diversi paragrafi per spiegare. Ad esempio, queste due linee

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

sono usati (a volte) nel ridurre x ad un valore vicino a 0 che differisce da x da un multiplo di π / 2, in particolare xn× π / 2. Il modo in cui questo viene fatto senza divisione o ramificazione è piuttosto intelligente. Ma non ci sono commenti!


Le versioni precedenti a 32 bit di GCC / glibc utilizzavano l' fsinistruzione, che è sorprendentemente inaccurata per alcuni ingressi. C'è un affascinante post sul blog che illustra questo con solo 2 righe di codice .

L'implementazione di fdlibm sinin C puro è molto più semplice di quella di glibc ed è ben commentata. Codice sorgente: fdlibm / s_sin.c e fdlibm / k_sin.c


35
Per vedere che questo è davvero il codice che gira su x86: compila un programma che chiama sin(); digitare gdb a.out, quindi break sin, quindi run, quindi disassemble.
Jason Orendorff,

5
@ Henry: non commettere l'errore di pensare che sia un buon codice però. È davvero terribile , non imparare a programmare in quel modo!
Thomas Bonini,

2
@Andreas Hmm, hai ragione, il codice IBM sembra piuttosto orribile rispetto a fdlibm. Ho modificato la risposta per aggiungere collegamenti alla routine seno di fdlibm.
Jason Orendorff,

3
@Henry: __kernel_sinè definito in k_sin.c, tuttavia, ed è puro C. Fai clic di nuovo su di esso: ho rovinato l'URL la prima volta.
Jason Orendorff,

3
Il codice sysdeps collegato è particolarmente interessante perché è correttamente arrotondato. Cioè, a quanto pare dà la risposta migliore possibile per tutti i valori di input, che è diventato possibile solo abbastanza di recente. In alcuni casi questo può essere lento perché potrebbe essere necessario calcolare molte cifre extra per garantire l'arrotondamento corretto. In altri casi è estremamente veloce - per numeri abbastanza piccoli la risposta è solo l'angolo.
Bruce Dawson,

66

Funzioni come seno e coseno sono implementate nel microcodice all'interno dei microprocessori. I chip Intel, ad esempio, hanno istruzioni di assemblaggio per questi. Il compilatore CA genererà il codice che chiama queste istruzioni di assemblaggio. (Al contrario, un compilatore Java non lo farà. Java valuta le funzioni di trigger nel software piuttosto che nell'hardware, e quindi funziona molto più lentamente.)

Patatine fritte non usano la serie Taylor per calcolare le funzioni di trigger, almeno non del tutto. Prima di tutto usano CORDIC , ma possono anche usare una breve serie di Taylor per perfezionare il risultato di CORDIC o per casi speciali come il seno sinusoidale con elevata precisione relativa per angoli molto piccoli. Per ulteriori spiegazioni, vedere questa risposta StackOverflow .


10
funzioni matematiche trascendentali come seno e coseno possono essere implementate nel microcodice o come istruzioni hardware negli attuali processori desktop e server a 32 bit. Questo non è sempre stato il caso, fino a quando i486 (DX) tutti i calcoli in virgola mobile sono stati eseguiti nel software ("soft-float") per la serie x86 senza un coprocessore separato. Non tutte (FPU) comprendevano funzioni trascendentali (ad esempio Weitek 3167).
mctylr,

1
Può essere più preciso? Come si fa a "lucidare" un'approssimazione usando una serie di Taylor?
Hank

4
Per quanto riguarda la "lucidatura" di una risposta, supponiamo di calcolare sia il seno che il coseno. Supponiamo che tu conosca il valore esatto di entrambi in un punto (ad es. Da CORDIC) ma desideri il valore in un punto vicino. Quindi per una piccola differenza h, puoi applicare le approssimazioni di Taylor f (x + h) = f (x) + h f '(x) or f (x + h) = f (x) + h f' (x) + h ^ 2 f '' (x) / 2.
John D. Cook,

6
I chip x86 / x64 hanno un'istruzione di assemblaggio per il calcolo del seno (fsin), ma questa istruzione a volte è piuttosto imprecisa e pertanto viene raramente utilizzata più. Vedi randomascii.wordpress.com/2014/10/10/09 per i dettagli. La maggior parte degli altri processori non ha istruzioni per il seno e il coseno perché il loro calcolo nel software offre maggiore flessibilità e potrebbe persino essere più veloce.
Bruce Dawson,

3
Le cose cordiali all'interno dei chip Intel non sono generalmente utilizzate. Innanzitutto, l'accuratezza e la risoluzione dell'operazione sono estremamente importanti per molte applicazioni. Cordic è notoriamente inaccurato quando si arriva alla settima cifra o giù di lì, e imprevedibile. In secondo luogo, ho sentito che esiste un bug nella loro implementazione, che causa ancora più problemi. Ho dato un'occhiata alla funzione sin per linux gcc, e abbastanza sicuro, usa chebyshev. il materiale incorporato non viene utilizzato. Oh, inoltre, l'algoritmo cordiale nel chip è più lento della soluzione software.
Donald Murray,

63

OK bambini, tempo per i professionisti ... Questa è una delle mie più grandi lamentele con ingegneri del software inesperti. Arrivano nel calcolo da zero delle funzioni trascendentali (usando la serie di Taylor) come se nessuno avesse mai fatto questi calcoli prima nella loro vita. Non vero. Questo è un problema ben definito ed è stato affrontato migliaia di volte da ingegneri software e hardware molto intelligenti e ha una soluzione ben definita. Fondamentalmente, la maggior parte delle funzioni trascendentali usa i polinomi di Chebyshev per calcolarli. Quanto a che polinomi sono usati dipende dalle circostanze. In primo luogo, la Bibbia su questo argomento è un libro intitolato "Computer Approximations" di Hart e Cheney. In quel libro, puoi decidere se hai un sommatore hardware, un moltiplicatore, un divisore, ecc. E decidere quali operazioni sono più veloci. ad es. se avessi un divisore molto veloce, il modo più veloce per calcolare il seno potrebbe essere P1 (x) / P2 (x) dove P1, P2 sono polinomi di Chebyshev. Senza il divisore veloce, potrebbe essere solo P (x), dove P ha molti più termini di P1 o P2 .... quindi sarebbe più lento. Quindi, il primo passo è determinare il tuo hardware e cosa può fare. Quindi si sceglie la combinazione appropriata di polinomi di Chebyshev (di solito è della forma cos (ax) = aP (x) per il coseno, ad esempio, sempre dove P è un polinomio di Chebyshev). Quindi decidi quale precisione decimale desideri. ad es. se si desidera una precisione di 7 cifre, la si cerca nella tabella appropriata nel libro che ho citato e vi darà (per precisione = 7,33) un numero N = 4 e un numero polinomiale 3502. N è l'ordine del polinomiale (quindi è p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0), perché N = 4. Quindi si cerca il valore effettivo di p4, p3, p2, p1, valori di p0 nella parte posteriore del libro sotto 3502 (saranno in virgola mobile). Quindi implementate il vostro algoritmo nel software nel formato: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... ed è così che calcolereste il coseno al 7 decimale posti su quell'hardware.

Si noti che la maggior parte delle implementazioni hardware delle operazioni trascendentali in una FPU di solito comporta alcuni microcodici e operazioni come questa (dipende dall'hardware). I polinomi di Chebyshev sono usati per la maggior parte dei trascendentali ma non per tutti. ad esempio, Square root è più veloce nell'usare una doppia iterazione del metodo Newton raphson usando prima una tabella di ricerca. Ancora una volta, quel libro "Computer Approximations" te lo dirà.

Se hai intenzione di implorare queste funzioni, consiglierei a chiunque di ottenere una copia di quel libro. È davvero la bibbia per questo tipo di algoritmi. Nota che ci sono un sacco di mezzi alternativi per calcolare questi valori come cordics, ecc., Ma questi tendono ad essere i migliori per algoritmi specifici in cui hai solo bisogno di bassa precisione. Per garantire sempre la precisione, i polinomi chebyshev sono la strada da percorrere. Come ho detto, problema ben definito. È stato risolto per 50 anni ormai ..... ed è così che è stato fatto.

Ora, detto questo, ci sono tecniche in base alle quali i polinomi di Chebyshev possono essere usati per ottenere un singolo risultato di precisione con un polinomio di basso grado (come nell'esempio sopra per il coseno). Quindi, ci sono altre tecniche per interpolare tra i valori per aumentare la precisione senza dover passare a un polinomio molto più ampio, come "Gal's Accurate Tables Method". Quest'ultima tecnica è a cui si riferisce il post riferito alla letteratura ACM. Ma alla fine, i polinomi di Chebyshev sono quelli che vengono utilizzati per ottenere il 90% del percorso.

Godere.


6
Non potrei essere più d'accordo con le prime frasi. Inoltre, vale la pena ricordare che il calcolo di funzioni speciali con precisione garantita è un problema difficile . Le persone intelligenti che menzioni trascorrono gran parte della loro vita a farlo. Inoltre, su una nota più tecnica, i polinomi min-max sono il ricercato graal, e i polinomi di Chebyshev sono proxy più semplici per loro.
Alexandre C.,

161
-1 per il tono poco professionale e sconclusionato (e lievemente maleducato), e per il fatto che l'attuale contenuto non ridondante di questa risposta, spogliata dell'escursione e della condiscendenza, si riduce sostanzialmente a "Usano spesso polinomi di Chebyshev; vedi questo libro per maggiori dettagli, è davvero bello! " Il che, sai, potrebbe essere assolutamente corretto, ma non è proprio il tipo di risposta autonoma che vogliamo qui su SO. Condensato in quel modo, avrebbe comunque fatto un commento decente sulla domanda.
Ilmari Karonen,

2
Nei primi anni di sviluppo del gioco, di solito veniva fatto con tabelle di ricerca che necessitavano di velocità). In genere non abbiamo usato le funzioni lib standard per queste cose.
cima al

4
Uso abbastanza spesso le tabelle di ricerca nei sistemi incorporati e i bittiani (anziché i radianti), ma questo è per un'applicazione specializzata (come i tuoi giochi). Penso che il ragazzo sia interessato a come il compilatore c calcola il peccato per i numeri in virgola mobile ....
Donald Murray

1
Ah, 50 anni fa. Ho iniziato a suonare con tali su Burroughs B220 con la serie McLaren. Successivamente hardware CDC e poi Motorola 68000. Arcsin era disordinato: ho scelto il quoziente di due polinomi e ho sviluppato il codice per trovare i coefficienti ottimali.
Rick James,

15

In sinparticolare, l'utilizzo dell'espansione Taylor ti darebbe:

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

continueresti ad aggiungere termini finché la differenza tra loro non è inferiore a un livello di tolleranza accettato o solo per un numero finito di passaggi (più veloce, ma meno preciso). Un esempio potrebbe essere qualcosa del tipo:

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

Nota: (1) funziona a causa dell'aproximation sin (x) = x per angoli piccoli. Per angoli più grandi è necessario calcolare sempre più termini per ottenere risultati accettabili. Puoi usare un argomento while e continuare con una certa precisione:

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
Se modifichi leggermente i coefficienti (e li codifichi in un polinomio), puoi interrompere prima circa 2 iterazioni.
Rick James,

14

Sì, ci sono anche algoritmi software per il calcolo sin. Fondamentalmente, il calcolo di questo tipo di cose con un computer digitale viene solitamente fatto usando metodi numerici come l'approssimazione della serie di Taylor che rappresenta la funzione.

I metodi numerici possono approssimare le funzioni a una quantità arbitraria di precisione e poiché la quantità di precisione che hai in un numero fluttuante è limitata, si adattano abbastanza bene a questi compiti.


12
Una vera implementazione probabilmente non utilizzerà una serie di Taylor, poiché ci sono modi più efficienti. Devi solo approssimarti correttamente nel dominio [0 ... pi / 2] e ci sono funzioni che forniranno una buona approssimazione in modo più efficiente rispetto a una serie di Taylor.
David Thornley,

2
@ David: sono d'accordo. Sono stato abbastanza attento da menzionare la parola "mi piace" nella mia risposta. Ma l'espansione di Taylor è semplice per spiegare l'idea alla base di metodi che approssimano le funzioni. Detto questo, ho visto implementazioni software (non sono sicuro che fossero ottimizzate) che utilizzavano le serie Taylor.
Mehrdad Afshari,

1
In realtà, le approssimazioni polinomiali sono uno dei modi più efficienti per calcolare le funzioni trigonometriche.
Jeremy Salwen,

13

Usa la serie Taylor e cerca di trovare una relazione tra i termini della serie in modo da non calcolare più e più volte le cose

Ecco un esempio di coseno:

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

usando questo possiamo ottenere il nuovo termine della somma usando quello già usato (evitiamo il fattoriale e x 2p )

spiegazione


2
Sapevi che puoi usare l'API di Google Chart per creare formule come questa usando TeX? code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer

11

È una domanda complessa. La CPU simile a Intel della famiglia x86 ha un'implementazione hardware della sin()funzione, ma fa parte della FPU x87 e non viene più utilizzata in modalità 64-bit (dove invece vengono utilizzati i registri SSE2). In quella modalità, viene utilizzata un'implementazione software.

Esistono diverse implementazioni di questo tipo. Uno è in fdlibm ed è usato in Java. Per quanto ne so, l'implementazione di glibc contiene parti di fdlibm e altre parti fornite da IBM.

Le implementazioni software di funzioni trascendentali come sin()tipicamente usano approssimazioni da polinomi, spesso ottenute dalla serie di Taylor.


3
I registri SSE2 non vengono utilizzati per calcolare sin (), né in modalità x86 né in modalità x64 e, naturalmente, sin viene calcolato in hardware indipendentemente dalla modalità. Ehi, è il 2010 in cui viviamo :)
Igor Korkhov,

7
@Igor: dipende da quale libreria matematica stai guardando. Si scopre che le librerie matematiche più ottimizzate su x86 usano implementazioni software SSE per sine cosche sono più veloci delle istruzioni hardware sull'FPU. Le librerie più semplici, più ingenue tendono a usare le istruzioni fsine fcos.
Stephen Canon,

@Stephen Canon: quelle librerie veloci hanno una precisione a 80 bit come fanno i registri FPU? Ho il sospetto subdolo che favoriscano la velocità rispetto alla precisione, che ovviamente è ragionevole in molti scenari, ad esempio nei giochi. E credo che il calcolo del seno con precisione a 32 bit utilizzando SSE e tabelle intermedie pre-calcolate potrebbe essere più veloce rispetto all'uso FSINcon la massima precisione. Le sarei molto grato se mi dicessi i nomi di quelle librerie veloci, è interessante dare un'occhiata.
Igor Korkhov,

@Igor: su x86 in modalità 64 bit, almeno su tutti i sistemi simili a Unix che conosco, la precisione è limitata a 64 bit, non i 79 bit della FPU x87. L'implementazione del software sin()sembra essere circa due volte più veloce di ciò che fsincalcola (proprio perché viene eseguita con meno precisione). Si noti che l'X87 è noto per avere una precisione leggermente inferiore rispetto ai suoi 79 bit annunciati.
Thomas Pornin,

1
In effetti, entrambe le implementazioni di sin () a 32 e 64 bit nelle librerie di runtime di msvc non usano l'istruzione FSIN. In realtà, danno risultati diversi, ad esempio sin (0.70444454416678126). Ciò comporterà 0,64761068800896837 (tolleranza entro 0,5 * (eps / 2)) in un programma a 32 bit e comporterà 0,64761068800896848 (errato) in uno a 64 bit.
e.tadeu,

9

I polinomi di Chebyshev, come menzionato in un'altra risposta, sono i polinomi in cui la più grande differenza tra la funzione e il polinomio è il più piccola possibile. È un inizio eccellente.

In alcuni casi, l'errore massimo non è quello che ti interessa, ma l'errore relativo massimo. Ad esempio per la funzione seno, l'errore vicino a x = 0 dovrebbe essere molto più piccolo che per valori più grandi; vuoi un piccolo errore relativo . Quindi calcoleresti il ​​polinomio di Chebyshev per sin x / x e moltiplicheresti quel polinomio per x.

Successivamente devi capire come valutare il polinomio. Si desidera valutarlo in modo tale che i valori intermedi siano piccoli e quindi gli errori di arrotondamento siano piccoli. Altrimenti gli errori di arrotondamento potrebbero diventare molto più grandi degli errori nel polinomio. E con funzioni come la funzione seno, se sei incurante, potrebbe essere possibile che il risultato che calcoli per sin x sia maggiore del risultato per sin y anche quando x <y. Sono quindi necessarie un'attenta scelta dell'ordine di calcolo e il calcolo dei limiti superiori per l'errore di arrotondamento.

Ad esempio, sin x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/5040 ... Se si calcola in modo ingenuo sin x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - x ^ 6/5040 ...), allora tale funzione in parentesi sta diminuendo, e si accadere che se y è il numero più grande successiva alla x, poi a volte sin y saranno più piccole di quelle sin x. Invece, calcola sin x = x - x ^ 3 * (1/6 - x ^ 2/120 + x ^ 4/5040 ...) dove ciò non può accadere.

Quando si calcolano i polinomi di Chebyshev, di solito è necessario arrotondare i coefficienti per raddoppiare la precisione, ad esempio. Ma mentre un polinomio di Chebyshev è ottimale, il polinomio di Chebyshev con coefficienti arrotondati alla doppia precisione non è il polinomio ottimale con coefficienti di doppia precisione!

Ad esempio per sin (x), dove hai bisogno di coefficienti per x, x ^ 3, x ^ 5, x ^ 7 ecc., Fai quanto segue: Calcola la migliore approssimazione di sin x con un polinomio (ax + bx ^ 3 + cx ^ 5 + dx ^ 7) con precisione superiore alla doppia, quindi arrotondare a per raddoppiare la precisione, dando A. La differenza tra a e A sarebbe piuttosto grande. Ora calcola la migliore approssimazione di (sin x - Ax) con un polinomio (bx ^ 3 + cx ^ 5 + dx ^ 7). Ottieni coefficienti diversi, perché si adattano alla differenza tra a e A. Round b per doppia precisione B. Quindi approssimativo (sin x - Ax - Bx ^ 3) con un polinomio cx ^ 5 + dx ^ 7 e così via. Otterrai un polinomio che è quasi buono come il polinomio originale di Chebyshev, ma molto meglio di Chebyshev arrotondato alla doppia precisione.

Successivamente dovresti tenere conto degli errori di arrotondamento nella scelta del polinomio. Hai trovato un polinomio con un errore minimo nel polinomio che ignora l'errore di arrotondamento, ma desideri ottimizzare il polinomio più l'errore di arrotondamento. Una volta che hai il polinomio di Chebyshev, puoi calcolare i limiti per l'errore di arrotondamento. Dì che f (x) è la tua funzione, P (x) è il polinomio ed E (x) è l'errore di arrotondamento. Non vuoi ottimizzare | f (x) - P (x) |, si desidera ottimizzare | f (x) - P (x) +/- E (x) |. Otterrai un polinomio leggermente diverso che cerca di mantenere gli errori polinomiali in basso quando l'errore di arrotondamento è grande e rilassa gli errori polinomiali un po 'dove l'errore di arrotondamento è piccolo.

Tutto ciò ti consentirà di arrotondare facilmente gli errori al massimo 0,55 volte l'ultimo bit, dove +, -, *, / avranno errori di arrotondamento al massimo 0,50 volte l'ultimo bit.


1
Questa è una bella spiegazione di come si può calcolare sin (x) in modo efficiente, ma non sembra proprio rispondere alla domanda dell'OP, che è specificamente su come le librerie / compilatori C comuni lo calcolano.
Ilmari Karonen,

I polinomi di Chebyshev minimizzano il valore assoluto massimo in un intervallo, ma non minimizzano la differenza più grande tra una funzione bersaglio e il polinomio. I polinomi di Minimax lo fanno.
Eric Postpischil,

9

Per quanto riguarda la funzione trigonometrica come sin(), dopo 5 anni cos(), tan()non è stato fatto menzione di un aspetto importante delle funzioni trig di alta qualità: riduzione della portata .

Un primo passo in una di queste funzioni è ridurre l'angolo, in radianti, ad un intervallo di un intervallo di 2 * π. Ma π è irrazionale, quindi riduzioni semplici come x = remainder(x, 2*M_PI)introdurre l'errore come M_PI, o machine pi, è un'approssimazione di π. Quindi, come si fa x = remainder(x, 2*π)?

Le prime biblioteche utilizzavano una precisione estesa o una programmazione artigianale per fornire risultati di qualità, ma comunque entro un intervallo limitato di double. Quando è stato richiesto un valore elevato sin(pow(2,30)), i risultati erano insignificanti o0.0 forse, con un flag di errore impostato su qualcosa come TLOSSperdita totale di precisione o PLOSSperdita parziale di precisione.

Una buona riduzione della gamma di valori di grandi dimensioni a un intervallo da -π a π è un problema impegnativo che rivaleggia con le sfide della funzione di trig base di base, come la sin()stessa.

Un buon rapporto è la riduzione degli Argomenti per grandi argomenti: buono fino all'ultimo bit (1992). Copre bene il problema: discute la necessità e come erano le cose su varie piattaforme (SPARC, PC, HP, 30+ altri) e fornisce un algoritmo di soluzione che fornisce risultati di qualità per tutti double da-DBL_MAX a DBL_MAX.


Se gli argomenti originali sono in gradi, ma possono avere un valore elevato, utilizzare fmod()prima per una maggiore precisione. Un buono fmod()non introdurrà alcun errore e quindi fornirà un'eccellente riduzione della portata.

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

Varie identità di trigoni remquo()offrono un miglioramento ancora maggiore. Esempio: sind ()


6

L'implementazione effettiva delle funzioni della libreria dipende dal compilatore specifico e / o dal fornitore della libreria. Che si tratti di hardware o software, che si tratti di un'espansione di Taylor o no, ecc., Varierà.

Mi rendo conto che non è assolutamente d'aiuto.


5

In genere sono implementati nel software e nella maggior parte dei casi non utilizzano le chiamate hardware corrispondenti (vale a dire l'assemblaggio). Tuttavia, come ha sottolineato Jason, questi sono specifici dell'implementazione.

Si noti che queste routine software non fanno parte delle fonti del compilatore, ma si troveranno piuttosto nella libreria di ricodifica come il clib o glibc per il compilatore GNU. Vedi http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

Se desideri un maggiore controllo, dovresti valutare attentamente ciò di cui hai bisogno. Alcuni dei metodi tipici sono l'interpolazione delle tabelle di ricerca, la chiamata di assembly (che spesso è lenta) o altri schemi di approssimazione come Newton-Raphson per le radici quadrate.


5

Se desideri un'implementazione nel software, non nell'hardware, il posto dove cercare una risposta definitiva a questa domanda è il Capitolo 5 delle Ricette numeriche . La mia copia è in una scatola, quindi non posso fornire dettagli, ma la versione breve (se ricordo bene) è che prendi tan(theta/2)come operazione primitiva e calcoli gli altri da lì. Il calcolo viene eseguito con un'approssimazione di serie, ma è qualcosa che converge molto più rapidamente di una serie di Taylor.

Mi dispiace non poter ricordare di più senza mettere la mano sul libro.


5

Non c'è niente come colpire la fonte e vedere come qualcuno l'ha effettivamente fatto in una libreria di uso comune; diamo un'occhiata all'implementazione di una libreria C in particolare. Ho scelto uLibC.

Ecco la funzione sin:

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

che sembra gestire alcuni casi speciali e quindi esegue una riduzione dell'argomento per mappare l'input nell'intervallo [-pi / 4, pi / 4], (suddividendo l'argomento in due parti, una grande e una coda) prima di chiamare

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

che quindi opera su quelle due parti. Se non c'è coda, viene generata una risposta approssimativa usando un polinomio di grado 13. Se c'è una coda, si ottiene una piccola aggiunta correttiva basata sul principio chesin(x+y) = sin(x) + sin'(x')y


4

Ogni volta che viene valutata una tale funzione, a un certo livello è molto probabile che:

  • Una tabella di valori che viene interpolata (per applicazioni veloci e imprecise, ad es. Computer grafica)
  • La valutazione di una serie che converge al valore desiderato --- probabilmente non una serie di Taylor, molto probabilmente qualcosa basata su una quadratura di fantasia come Clenshaw-Curtis.

Se non è disponibile alcun supporto hardware, il compilatore probabilmente utilizza quest'ultimo metodo, emettendo solo codice assembler (senza simboli di debug), anziché utilizzare la libreria ac --- rendendoti difficile rintracciare il codice effettivo nel tuo debugger.


4

Come molte persone hanno sottolineato, dipende dall'implementazione. Ma per quanto ho capito la tua domanda, eri interessato a una vera implementazione software delle funzioni matematiche, ma non sei riuscito a trovarne una. In questo caso, eccoti qui:

  • Scarica il codice sorgente di glibc da http://ftp.gnu.org/gnu/glibc/
  • Guarda il file che si dosincos.ctrova nella cartella glibc root \ sysdeps \ ieee754 \ dbl-64 decompressa
  • Allo stesso modo è possibile trovare implementazioni del resto della libreria matematica, basta cercare il file con il nome appropriato

Puoi anche dare un'occhiata ai file con l' .tblestensione, il loro contenuto non è altro che enormi tabelle di valori pre - calcolati di diverse funzioni in forma binaria. Ecco perché l'implementazione è così veloce: invece di calcolare tutti i coefficienti di qualunque serie utilizzino, eseguono solo una rapida ricerca, che è molto più veloce. A proposito, usano le serie Tailor per calcolare seno e coseno.

Spero che aiuti.


4

Proverò a rispondere per il caso di sin()un programma C, compilato con il compilatore C di GCC su un attuale processore x86 (diciamo un Intel Core 2 Duo).

Nel linguaggio C la libreria C standard include funzioni matematiche comuni, non inclusi nel linguaggio stesso (ad esempio pow, sine cosper il potere, seno, coseno e rispettivamente). Le intestazioni sono incluse in math.h .

Ora su un sistema GNU / Linux, queste funzioni delle librerie sono fornite da glibc (GNU libc o GNU C Library). Ma il compilatore GCC vuole che tu ti colleghi alla libreria matematica ( libm.so) usando il -lmflag del compilatore per abilitare l'uso di queste funzioni matematiche. Non sono sicuro del perché non faccia parte della libreria C standard. Si tratterebbe di una versione software delle funzioni in virgola mobile o "soft-float".

A parte: la ragione per cui le funzioni matematiche sono separate è storica, e aveva semplicemente lo scopo di ridurre le dimensioni dei programmi eseguibili in sistemi Unix molto vecchi, possibilmente prima che fossero disponibili librerie condivise, per quanto ne so.

Ora il compilatore può ottimizzare la funzione di libreria C standard sin()(fornita da libm.so) per essere sostituita con una chiamata a un'istruzione nativa alla funzione sin () integrata della CPU / FPU, che esiste come istruzione FPU ( FSINper x86 / x87) su processori più recenti come la serie Core 2 (questo è corretto fino all'i486DX). Ciò dipende dai flag di ottimizzazione passati al compilatore gcc. Se si dicesse al compilatore di scrivere codice che verrebbe eseguito su qualsiasi i386 o processore più recente, non si farebbe una tale ottimizzazione. La -mcpu=486bandiera informerebbe il compilatore che era sicuro effettuare tale ottimizzazione.

Ora, se il programma eseguisse la versione software della funzione sin (), lo farebbe sulla base di un algoritmo CORDIC (COordinate Rotation DIgital Computer) o BKM , o più probabilmente di una tabella o di un calcolo della serie di potenza che viene comunemente utilizzato ora per calcolare tali funzioni trascendentali. [Src: http://en.wikipedia.org/wiki/Cordic#Application]

Qualsiasi versione recente (dal 2.9x circa) di gcc offre anche una versione integrata di sin, __builtin_sin()che verrà utilizzata per sostituire la chiamata standard alla versione della libreria C, come ottimizzazione.

Sono sicuro che sia chiaro come il fango, ma spero che ti dia più informazioni di quanto ti aspettassi e molti punti da saltare per imparare di più da solo.



3

Non usare la serie Taylor. I polinomi di Chebyshev sono sia più veloci che più precisi, come sottolineato da un paio di persone sopra. Ecco un'implementazione (originariamente dalla ROM ZX Spectrum): https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
Questo non sembra rispondere alla domanda come è stato chiesto. L'OP chiede come sono le funzioni di attivazione calcolate le dai comuni compilatori / librerie C (e sono abbastanza sicuro che ZX Spectrum non sia idoneo), non come dovrebbero essere calcolate. Questo potrebbe essere stato un utile commento su alcune delle risposte precedenti, però.
Ilmari Karonen,

1
Ah, hai ragione. Avrebbe dovuto essere un commento e non una risposta. Non uso SO da un po 'di tempo e ho dimenticato come funziona il sistema. Comunque, penso che l'implementazione di Spectrum sia rilevante perché aveva una CPU molto lenta e la velocità era essenziale. L'algoritmo migliore quindi è sicuramente ancora abbastanza buono, quindi sarebbe una buona idea per le librerie C implementare le funzioni di trigger usando i polinomi di Chebyshev.
Albert Veli,

2

Il calcolo di seno / coseno / tangente è in realtà molto semplice da eseguire tramite il codice utilizzando la serie Taylor. Scriverne uno da soli richiede 5 secondi.

L'intero processo può essere riassunto con questa equazione qui:

espansione del peccato e dei costi

Ecco alcune routine che ho scritto per 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
Si tratta di un'implementazione piuttosto negativa poiché non utilizza che i termini successivi delle serie seno e coseno abbiano quozienti molto semplici. Ciò significa che si può ridurre il numero di moltiplicazioni e divisioni da O (n ^ 2) qui a O (n). Ulteriori riduzioni si ottengono dimezzando e quadrando come per esempio nella libreria matematica bc (calcolatrice multiprecisione POSIX).
Lutz Lehmann,

2
Inoltre non sembra rispondere alla domanda come posta; l'OP chiede come vengono calcolate le funzioni di trigger dai comuni compilatori / librerie C, non per reimplementazioni personalizzate.
Ilmari Karonen,

2
Penso che sia una buona risposta in quanto risponde allo spirito della domanda che (e posso solo immaginare ovviamente) la curiosità su una "scatola nera" altrimenti funziona come sin (). È l'unica risposta qui che ti dà la possibilità di capire rapidamente cosa sta succedendo passandoci sopra in pochi secondi invece di leggere un codice sorgente C ottimizzato.
Mike M,

infatti le biblioteche usano la versione molto più ottimizzata, rendendosi conto che una volta che hai un termine, puoi ottenere il termine successivo moltiplicando alcuni valori. Vedi un esempio nella risposta di Blindy . Stai calcolando il potere e i fattoriali ancora e ancora che è molto più lento
phuclv,


0

Versione migliorata del codice dalla risposta di 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

L'essenza di come ciò si trova in questo estratto da Applied Numerical Analysis di Gerald Wheatley:

Quando il tuo programma software chiede al computer di ottenere un valore inserisci qui la descrizione dell'immagineo inserisci qui la descrizione dell'immagine, ti sei mai chiesto come può ottenere i valori se le funzioni più potenti che può calcolare sono i polinomi? Non li cerca nelle tabelle e non interpola! Piuttosto, il computer approssima ogni funzione diversa dai polinomi da un polinomio su misura per fornire i valori in modo molto accurato.

Alcuni punti da menzionare su quanto sopra è che alcuni algoritmi infatti interpolano da una tabella, anche se solo per le prime iterazioni. Nota anche come menziona il fatto che i computer utilizzano i polinomi approssimativi senza specificare quale tipo di polinomio approssimativo. Come hanno sottolineato altri nel thread, i polinomi di Chebyshev sono più efficienti dei polinomi di Taylor in questo caso.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.