Comment C calcule-t-il sin () et d'autres fonctions mathématiques?


248

J'ai étudié les désassemblages .NET et le code source de GCC, mais je n'arrive pas à trouver nulle part l'implémentation réelle de sin()et d'autres fonctions mathématiques ... ils semblent toujours faire référence à autre chose.

Quelqu'un peut-il m'aider à les trouver? J'ai l'impression qu'il est peu probable que TOUS les matériels sur lesquels C fonctionnera prennent en charge les fonctions trig dans le matériel, donc il doit y avoir un algorithme logiciel quelque part , non?


Je suis au courant de plusieurs façons dont les fonctions peuvent être calculées, et ai écrit mes propres routines aux fonctions utilisant des séries calcul taylor pour le plaisir. Je suis curieux de savoir à quel point les langages de production sont réels, car toutes mes implémentations sont toujours plus lentes de plusieurs ordres de grandeur, même si je pense que mes algorithmes sont assez intelligents (évidemment ils ne le sont pas).


2
Veuillez noter que cette implémentation dépend. Vous devez spécifier l'implémentation qui vous intéresse le plus.
jason

3
J'ai marqué .NET et C parce que j'ai regardé dans les deux endroits et je n'ai pas pu comprendre non plus. Bien qu'en regardant le démontage .NET, il semble qu'il puisse appeler en C non géré, pour autant que je sache, ils ont la même implémentation.
Hank

Réponses:


213

Dans GNU libm, l'implémentation de sindépend du système. Par conséquent, vous pouvez trouver l'implémentation, pour chaque plate-forme, quelque part dans le sous-répertoire approprié de sysdeps .

Un répertoire comprend une implémentation en C, fournie par IBM. Depuis octobre 2011, c'est le code qui s'exécute réellement lorsque vous appelez sin()sur un système Linux x86-64 typique. Il est apparemment plus rapide que les fsininstructions de montage. Code source: sysdeps / ieee754 / dbl-64 / s_sin.c , recherchez __sin (double x).

Ce code est très complexe. Aucun algorithme logiciel n'est aussi rapide que possible et aussi précis sur toute la plage de valeurs x , de sorte que la bibliothèque implémente plusieurs algorithmes différents, et son premier travail consiste à examiner x et à décider quel algorithme utiliser.

  • Lorsque x est très très proche de 0, sin(x) == xc'est la bonne réponse.

  • Un peu plus loin, sin(x)utilise la fameuse série Taylor. Cependant, ce n'est précis que près de 0, alors ...

  • Lorsque l'angle est supérieur à environ 7 °, un algorithme différent est utilisé, calculant les approximations de la série de Taylor pour sin (x) et cos (x), puis en utilisant les valeurs d'une table précalculée pour affiner l'approximation.

  • Quand | x | > 2, aucun des algorithmes ci-dessus ne fonctionnerait, donc le code commence par calculer une valeur plus proche de 0 qui peut être introduite sinou à la cosplace.

  • Il y a encore une autre branche pour faire face à x étant un NaN ou une infinité.

Ce code utilise des hacks numériques que je n'ai jamais vus auparavant, bien que je sache qu'ils sont bien connus des experts en virgule flottante. Parfois, quelques lignes de code prenaient plusieurs paragraphes pour expliquer. Par exemple, ces deux lignes

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

sont utilisés (parfois) pour réduire x à une valeur proche de 0 qui diffère de x par un multiple de π / 2, en particulier xn× π / 2. La façon de procéder sans division ni ramification est plutôt intelligente. Mais il n'y a aucun commentaire!


Les anciennes versions 32 bits de GCC / glibc utilisaient l' fsininstruction, ce qui est étonnamment inexact pour certaines entrées. Il y a un article de blog fascinant illustrant cela avec seulement 2 lignes de code .

L'implémentation de fdlibm sinen C pur est beaucoup plus simple que celle de glibc et est bien commentée. Code source: fdlibm / s_sin.c et fdlibm / k_sin.c


35
Pour voir que c'est vraiment le code qui s'exécute sur x86: compilez un programme qui appelle sin(); tapez gdb a.out, puis break sin, puis run, alors disassemble.
Jason Orendorff

5
@Henry: ne faites pas l'erreur de penser que c'est du bon code. C'est vraiment terrible , n'apprends pas à coder de cette façon!
Thomas Bonini

2
@Andreas Hmm, vous avez raison, le code IBM a l'air plutôt horrible par rapport à fdlibm. J'ai édité la réponse pour ajouter des liens vers la routine sinus de fdlibm.
Jason Orendorff

3
@Henry: __kernel_sinest défini dans k_sin.c, cependant, et c'est du pur C. Cliquez à nouveau dessus - j'ai bâclé l'URL la première fois.
Jason Orendorff

3
Le code sysdeps lié est particulièrement intéressant car il est correctement arrondi. Autrement dit, il donne apparemment la meilleure réponse possible pour toutes les valeurs d'entrée, ce qui n'est devenu possible que récemment. Dans certains cas, cela peut être lent car de nombreux chiffres supplémentaires doivent être calculés pour garantir un arrondi correct. Dans d'autres cas, c'est extrêmement rapide - pour des nombres suffisamment petits, la réponse n'est que l'angle.
Bruce Dawson

66

Des fonctions comme le sinus et le cosinus sont implémentées dans le microcode à l'intérieur des microprocesseurs. Les puces Intel, par exemple, ont des instructions de montage pour ces derniers. Le compilateur AC va générer du code qui appelle ces instructions d'assemblage. (En revanche, un compilateur Java ne le fera pas. Java évalue les fonctions trigonométriques dans le logiciel plutôt que dans le matériel, et il s'exécute donc beaucoup plus lentement.)

Les puces n'utilisent pas la série Taylor pour calculer les fonctions trigonométriques, du moins pas entièrement. Tout d'abord, ils utilisent CORDIC , mais ils peuvent également utiliser une courte série Taylor pour peaufiner le résultat de CORDIC ou pour des cas particuliers tels que le calcul sinus avec une précision relative élevée pour de très petits angles. Pour plus d'explications, consultez cette réponse StackOverflow .


10
les fonctions mathématiques transcendantales comme le sinus et le cosinus peuvent être implémentées dans un microcode ou comme des instructions matérielles dans les processeurs de bureau et de serveur 32 bits actuels. Ce n'était pas toujours le cas, jusqu'à ce que le i486 (DX) tous les calculs en virgule flottante soient effectués dans un logiciel ("soft-float") pour la série x86 sans coprocesseur séparé. Les FPU ne comprenaient pas tous des fonctions transcendantales (par exemple Weitek 3167).
mctylr

1
Peux-tu être plus précis? Comment «peaufiner» une approximation en utilisant une série de Taylor?
Hank

4
Pour ce qui est de «peaufiner» une réponse, supposons que vous calculez à la fois le sinus et le cosinus. Supposons que vous connaissiez la valeur exacte des deux à un moment donné (par exemple de CORDIC) mais que vous vouliez la valeur à un point proche. Alors pour une petite différence h, vous pouvez appliquer les approximations de Taylor f (x + h) = f (x) + h f '(x) ou f (x + h) = f (x) + h f' (x) + h ^ 2 f '' (x) / 2.
John D. Cook

6
Les puces x86 / x64 ont une instruction d'assemblage pour calculer le sinus (fsin) mais cette instruction est parfois assez inexacte et est donc rarement utilisée. Voir randomascii.wordpress.com/2014/10/09/… pour plus de détails. La plupart des autres processeurs n'ont pas d'instructions pour le sinus et le cosinus car leur calcul dans le logiciel donne plus de flexibilité, et peut même être plus rapide.
Bruce Dawson

3
Les trucs cordiques à l'intérieur des puces Intel ne sont généralement PAS utilisés. Premièrement, la précision et la résolution de l'opération sont extrêmement importantes pour de nombreuses applications. Cordic est notoirement inexact lorsque vous arrivez au 7e chiffre environ, et imprévisible. Deuxièmement, j'ai entendu qu'il y avait un bug dans leur implémentation, ce qui causait encore plus de problèmes. J'ai jeté un œil à la fonction sin pour linux gcc, et bien sûr, elle utilise chebyshev. le truc intégré n'est pas utilisé. Oh, aussi, l'algorithme cordique de la puce est plus lent que la solution logicielle.
Donald Murray

63

OK kiddies, il est temps pour les pros .... C'est l'une de mes plus grosses plaintes avec les ingénieurs logiciels inexpérimentés. Ils arrivent à calculer les fonctions transcendantales à partir de zéro (en utilisant la série de Taylor) comme si personne n'avait jamais fait ces calculs auparavant dans leur vie. Pas vrai. Il s'agit d'un problème bien défini qui a été abordé des milliers de fois par des ingénieurs logiciels et matériels très intelligents et qui a une solution bien définie. Fondamentalement, la plupart des fonctions transcendantales utilisent des polynômes de Chebyshev pour les calculer. Le choix des polynômes dépend des circonstances. Premièrement, la Bible sur cette question est un livre intitulé "Computer Approximations" par Hart et Cheney. Dans ce livre, vous pouvez décider si vous avez un additionneur matériel, un multiplicateur, un diviseur, etc., et décider quelles opérations sont les plus rapides. par exemple, si vous aviez un séparateur très rapide, le moyen le plus rapide pour calculer le sinus pourrait être P1 (x) / P2 (x) où P1, P2 sont des polynômes de Chebyshev. Sans le diviseur rapide, ce pourrait être juste P (x), où P a beaucoup plus de termes que P1 ou P2 .... donc ce serait plus lent. La première étape consiste donc à déterminer votre matériel et ce qu'il peut faire. Ensuite, vous choisissez la combinaison appropriée de polynômes de Chebyshev (est généralement de la forme cos (ax) = aP (x) pour le cosinus par exemple, là encore où P est un polynôme de Chebyshev). Ensuite, vous décidez quelle précision décimale vous souhaitez. Par exemple, si vous voulez une précision à 7 chiffres, vous regardez cela dans le tableau approprié dans le livre que j'ai mentionné, et cela vous donnera (pour une précision = 7,33) un nombre N = 4 et un nombre polynomial 3502. N est l'ordre du polynôme (c'est donc p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0), car N = 4. Ensuite, vous recherchez la valeur réelle des p4, p3, p2, p1, valeurs p0 à l'arrière du livre sous 3502 (elles seront en virgule flottante). Ensuite, vous implémentez votre algorithme dans un logiciel sous la forme: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... et voici comment vous calculeriez le cosinus à 7 décimales place sur ce matériel.

Notez que la plupart des implémentations matérielles d'opérations transcendantales dans une FPU impliquent généralement un microcode et des opérations comme celle-ci (dépend du matériel). Les polynômes de Chebyshev sont utilisés pour la plupart des transcendantaux mais pas tous. Par exemple, la racine carrée est plus rapide pour utiliser une double itération de la méthode Newton raphson en utilisant d'abord une table de recherche. Encore une fois, ce livre "Computer Approximations" vous le dira.

Si vous prévoyez d'implémenter ces fonctions, je recommanderais à quiconque d'obtenir une copie de ce livre. C'est vraiment la Bible pour ce genre d'algorithmes. Notez qu'il existe des tas de moyens alternatifs pour calculer ces valeurs comme les cordiques, etc., mais ceux-ci ont tendance à être les meilleurs pour des algorithmes spécifiques où vous n'avez besoin que d'une faible précision. Pour garantir la précision à chaque fois, les polynômes de Chebyshev sont la voie à suivre. Comme je l'ai dit, problème bien défini. A été résolu depuis 50 ans maintenant ..... et c'est comme ça que ça se fait.

Cela étant dit, il existe des techniques permettant d'utiliser les polynômes de Chebyshev pour obtenir un résultat de précision unique avec un polynôme de faible degré (comme l'exemple ci-dessus pour le cosinus). Ensuite, il existe d'autres techniques pour interpoler entre les valeurs pour augmenter la précision sans avoir à passer à un polynôme beaucoup plus grand, comme la «méthode des tables précises de Gal». Cette dernière technique est ce à quoi fait référence la publication faisant référence à la littérature ACM. Mais en fin de compte, les polynômes de Chebyshev sont ce qui est utilisé pour obtenir 90% du chemin.

Prendre plaisir.


6
Je ne pourrais pas être plus d'accord avec les premières phrases. Il convient également de rappeler que le calcul de fonctions spéciales avec une précision garantie est un problème difficile . Les personnes intelligentes que vous mentionnez passent la majeure partie de leur vie à faire cela. De plus, sur une note plus technique, les polynômes min-max sont le graal recherché, et les polynômes de Chebyshev sont des procurations plus simples pour eux.
Alexandre C.

161
-1 pour le ton non professionnel et décousu (et légèrement grossier), et pour le fait que le contenu réel non redondant de cette réponse, dépourvu de déchaînement et de condescendance, se résume essentiellement à "Ils utilisent souvent des polynômes de Chebyshev; voir ce livre pour plus de détails, c'est vraiment bien! " Ce qui, vous le savez, pourrait bien être absolument correct, mais ce n'est pas vraiment le genre de réponse autonome que nous voulons ici sur SO. Condensé comme ça, il aurait cependant fait un commentaire décent sur la question.
Ilmari Karonen

2
Au début des années de développement de jeux, cela se faisait généralement avec des tables de recherche (besoin critique de vitesse). Nous n'avons généralement pas utilisé les fonctions lib standard pour ces choses.
topspin

4
J'utilise assez souvent des tables de recherche dans des systèmes embarqués et des bittians (au lieu de radians), mais c'est pour une application spécialisée (comme vos jeux). Je pense que le gars s'intéresse à la façon dont le compilateur c calcule le péché pour les nombres à virgule flottante ....
Donald Murray

1
Ah, il y a 50 ans. J'ai commencé à jouer avec de tels sur le Burroughs B220 avec la série McLaren. Plus tard, le matériel CDC, puis le Motorola 68000. Arcsin était désordonné - j'ai choisi le quotient de deux polynômes et développé du code pour trouver les coefficients optimaux.
Rick James

15

En sinparticulier, l'utilisation de l'extension Taylor vous donnerait:

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

vous continueriez à ajouter des termes jusqu'à ce que la différence entre eux soit inférieure à un niveau de tolérance accepté ou juste pour un nombre fini d'étapes (plus rapide, mais moins précis). Un exemple serait quelque chose comme:

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

Remarque: (1) fonctionne en raison de l'aproximation sin (x) = x pour les petits angles. Pour des angles plus grands, vous devez calculer de plus en plus de termes pour obtenir des résultats acceptables. Vous pouvez utiliser un argument while et continuer avec une certaine précision:

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
Si vous modifiez un peu les coefficients (et les codez en dur dans un polynôme), vous pouvez arrêter environ 2 itérations plus tôt.
Rick James

14

Oui, il existe également des algorithmes logiciels pour le calcul sin. Fondamentalement, le calcul de ce genre de choses avec un ordinateur numérique se fait généralement à l'aide de méthodes numériques telles que l'approximation de la série de Taylor représentant la fonction.

Les méthodes numériques peuvent approximer les fonctions avec une précision arbitraire et comme la précision que vous avez dans un nombre flottant est limitée, elles conviennent assez bien à ces tâches.


12
Une véritable implémentation n'utilisera probablement pas une série Taylor, car il existe des moyens plus efficaces. Vous avez seulement besoin d'approximer correctement dans le domaine [0 ... pi / 2], et il y a des fonctions qui fourniront une bonne approximation plus efficacement qu'une série Taylor.
David Thornley

2
@David: Je suis d'accord. J'ai été assez prudent pour mentionner le mot «comme» dans ma réponse. Mais l'expansion de Taylor est simple pour expliquer l'idée derrière des méthodes qui rapprochent des fonctions. Cela dit, j'ai vu des implémentations logicielles (je ne sais pas si elles étaient optimisées) qui utilisaient la série Taylor.
Mehrdad Afshari

1
En fait, les approximations polynomiales sont l'un des moyens les plus efficaces pour calculer les fonctions trigonométriques.
Jeremy Salwen

13

Utilisez la série Taylor et essayez de trouver une relation entre les termes de la série afin de ne pas calculer les choses encore et encore

Voici un exemple pour 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;
}

en utilisant cela, nous pouvons obtenir le nouveau terme de la somme en utilisant celui déjà utilisé (nous évitons le factoriel et x 2p )

explication


2
Saviez-vous que vous pouvez utiliser l'API Google Chart pour créer des formules comme celle-ci à l'aide de TeX? code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer

11

C'est une question complexe. Le processeur Intel de la famille x86 a une implémentation matérielle de la sin()fonction, mais elle fait partie du FPU x87 et n'est plus utilisée en mode 64 bits (où les registres SSE2 sont utilisés à la place). Dans ce mode, une implémentation logicielle est utilisée.

Il existe plusieurs implémentations de ce type. L'un est dans fdlibm et est utilisé en Java. Pour autant que je sache, l'implémentation de la glibc contient des parties de fdlibm et d'autres parties apportées par IBM.

Les implémentations logicielles de fonctions transcendantales telles que sin()généralement utilisent des approximations par des polynômes, souvent obtenus à partir de la série Taylor.


3
Les registres SSE2 ne sont pas utilisés pour calculer sin (), ni en mode x86 ni en mode x64 et, bien sûr, sin est calculé dans le matériel quel que soit le mode. Hé, c'est 2010 dans
lequel

7
@Igor: cela dépend de la bibliothèque mathématique que vous regardez. Il s'avère que les bibliothèques mathématiques les plus optimisées sur x86 utilisent des implémentations logicielles SSE pour sinet cosqui sont plus rapides que les instructions matérielles sur le FPU. Les bibliothèques plus simples et plus naïves ont tendance à utiliser les instructions fsinet fcos.
Stephen Canon

@Stephen Canon: Ces bibliothèques rapides ont-elles une précision de 80 bits comme le font les registres FPU? Je soupçonne très sournoisement qu'ils préfèrent la vitesse à la précision, ce qui est bien sûr raisonnable dans de nombreux scénarios, par exemple dans les jeux. Et je crois que le calcul du sinus avec une précision de 32 bits en utilisant SSE et des tables intermédiaires précalculées pourrait être plus rapide qu'en utilisant FSINavec une précision totale. Je serais très reconnaissant si vous me disiez le nom de ces bibliothèques rapides, c'est intéressant d'y jeter un coup d'œil.
Igor Korkhov

@Igor: sur x86 en mode 64 bits, au moins sur tous les systèmes de type Unix que je connais, la précision est limitée à 64 bits, pas aux 79 bits du FPU x87. L'implémentation logicielle de sin()se trouve être environ deux fois plus rapide que ce qui est fsincalculé (précisément parce qu'elle se fait avec moins de précision). Notez que le x87 est connu pour avoir une précision réelle un peu inférieure à ses 79 bits annoncés.
Thomas Pornin

1
En effet, les implémentations 32 et 64 bits de sin () dans les bibliothèques d'exécution msvc n'utilisent pas l'instruction FSIN. En fait, ils donnent des résultats différents, prenez par exemple le péché (0.70444454416678126). Cela se traduira par 0,64761068800896837 (à droite avec une tolérance de 0,5 * (eps / 2)) dans un programme 32 bits, et se traduira par 0,64761068800896848 (faux) dans un programme 64 bits.
e.tadeu

9

Les polynômes de Tchebychev, comme mentionné dans une autre réponse, sont les polynômes où la plus grande différence entre la fonction et le polynôme est aussi petite que possible. C'est un excellent début.

Dans certains cas, l'erreur maximale n'est pas celle qui vous intéresse, mais l'erreur relative maximale. Par exemple pour la fonction sinus, l'erreur près de x = 0 devrait être beaucoup plus petite que pour des valeurs plus grandes; vous voulez une petite erreur relative . Donc, vous calculeriez le polynôme de Tchebychev pour sin x / x, et multiplieriez ce polynôme par x.

Ensuite, vous devez comprendre comment évaluer le polynôme. Vous voulez l'évaluer de telle manière que les valeurs intermédiaires soient petites et donc que les erreurs d'arrondi soient petites. Sinon, les erreurs d'arrondi pourraient devenir beaucoup plus importantes que les erreurs dans le polynôme. Et avec des fonctions comme la fonction sinus, si vous êtes insouciant, il peut être possible que le résultat que vous calculez pour sin x soit supérieur au résultat pour sin y même lorsque x <y. Il faut donc choisir soigneusement l'ordre de calcul et le calcul des limites supérieures pour l'erreur d'arrondi.

Par exemple, sin x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/5040 ... Si vous calculez naïvement sin x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - x ^ 6/5040 ...), alors cette fonction entre parenthèses est en baisse, et il va arriver que si y est le numéro suivant plus à x, alors le péché parfois y sera plus petit que le péché x. Au lieu de cela, calculez sin x = x - x ^ 3 * (1/6 - x ^ 2/120 + x ^ 4/5040 ...) où cela ne peut pas se produire.

Lors du calcul des polynômes de Chebyshev, vous devez généralement arrondir les coefficients pour doubler la précision, par exemple. Mais alors qu'un polynôme de Chebyshev est optimal, le polynôme de Chebyshev avec des coefficients arrondis à la double précision n'est pas le polynôme optimal avec des coefficients de double précision!

Par exemple, pour sin (x), où vous avez besoin de coefficients pour x, x ^ 3, x ^ 5, x ^ 7 etc. vous procédez comme suit: Calculez la meilleure approximation de sin x avec un polynôme (ax + bx ^ 3 + cx ^ 5 + dx ^ 7) avec une précision supérieure à la double, puis arrondissez de a à la double précision, ce qui donne A. La différence entre a et A serait assez grande. Calculez maintenant la meilleure approximation de (sin x - Ax) avec un polynôme (bx ^ 3 + cx ^ 5 + dx ^ 7). Vous obtenez des coefficients différents, car ils s'adaptent à la différence entre a et A. Arrondissez b à une double précision B. Puis approximez (sin x - Ax - Bx ^ 3) avec un polynôme cx ^ 5 + dx ^ 7 et ainsi de suite. Vous obtiendrez un polynôme qui est presque aussi bon que le polynôme original de Chebyshev, mais bien meilleur que Chebyshev arrondi à la double précision.

Ensuite, vous devez prendre en compte les erreurs d'arrondi dans le choix du polynôme. Vous avez trouvé un polynôme avec une erreur minimale dans le polynôme en ignorant l'erreur d'arrondi, mais vous souhaitez optimiser le polynôme plus l'erreur d'arrondi. Une fois que vous avez le polynôme de Chebyshev, vous pouvez calculer les limites de l'erreur d'arrondi. Supposons que f (x) est votre fonction, P (x) est le polynôme et E (x) est l'erreur d'arrondi. Vous ne voulez pas optimiser | f (x) - P (x) |, vous souhaitez optimiser | f (x) - P (x) +/- E (x) |. Vous obtiendrez un polynôme légèrement différent qui essaie de limiter les erreurs polynomiales là où l'erreur d'arrondi est importante et assouplit un peu les erreurs polynomiales là où l'erreur d'arrondi est petite.

Tout cela vous permettra d'obtenir facilement des erreurs d'arrondi d'au plus 0,55 fois le dernier bit, où +, -, *, / ont des erreurs d'arrondi d'au plus 0,50 fois le dernier bit.


1
C'est une belle explication de la façon dont on peut calculer sin (x) efficacement, mais cela ne semble pas vraiment répondre à la question de l'OP, qui concerne spécifiquement la façon dont les bibliothèques / compilateurs C courants le calculent.
Ilmari Karonen

Les polynômes de Chebyshev minimisent la valeur absolue maximale sur un intervalle, mais ils ne minimisent pas la plus grande différence entre une fonction cible et le polynôme. Les polynômes Minimax le font.
Eric Postpischil

9

En ce qui concerne la fonction trigonométrique comme sin(), cos(), tan()il n'y a pas eu de mention, au bout de 5 ans, d'un aspect important des fonctions trigonométriques de haute qualité: réduction Gamme .

L'une des premières étapes de l'une de ces fonctions consiste à réduire l'angle, en radians, à une plage d'un intervalle de 2 * π. Mais π est irrationnel, donc des réductions simples comme x = remainder(x, 2*M_PI)introduire l'erreur comme M_PI, ou machine pi, sont une approximation de π. Alors, comment faire x = remainder(x, 2*π)?

Les premières bibliothèques ont utilisé une précision étendue ou une programmation spécialement conçue pour donner des résultats de qualité, mais toujours sur une plage limitée double. Lorsqu'une grande valeur était demandée comme sin(pow(2,30)), les résultats étaient sans signification ou 0.0et peut-être avec un indicateur d'erreur réglé sur quelque chose comme TLOSSune perte totale de précision ou PLOSSune perte partielle de précision.

Une bonne réduction de la plage de grandes valeurs à un intervalle comme -π à π est un problème difficile qui rivalise avec les défis de la fonction trig de base, comme sin()elle-même.

Un bon rapport est la réduction d'arguments pour des arguments énormes: bon jusqu'au dernier (1992). Il couvre bien le problème: discute du besoin et de la façon dont les choses se passaient sur différentes plates-formes (SPARC, PC, HP, 30+ autres) et fournit un algorithme de solution qui donne des résultats de qualité pour tous double du -DBL_MAXau DBL_MAX.


Si les arguments d'origine sont en degrés, mais peuvent être de grande valeur, utilisez d' fmod()abord pour une meilleure précision. Un bon fmod()n'introduira aucune erreur et fournira donc une excellente réduction de plage.

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

Différentes identités trigonométriques et remquo()offrent encore plus d'amélioration. Exemple: sind ()


6

L'implémentation réelle des fonctions de bibliothèque dépend du compilateur et / ou du fournisseur de bibliothèque spécifique. Que cela soit fait en matériel ou en logiciel, qu'il s'agisse d'une extension Taylor ou non, etc., cela variera.

Je me rends compte que ce n'est absolument pas utile.


5

Ils sont généralement implémentés dans un logiciel et n'utilisent pas le matériel correspondant (c'est-à-dire un assemblage) dans la plupart des cas. Cependant, comme Jason l'a souligné, ce sont des implémentations spécifiques.

Notez que ces routines logicielles ne font pas partie des sources du compilateur, mais seront plutôt trouvées dans la bibliothèque de correspodage telle que clib ou glibc pour le compilateur GNU. Voir http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

Si vous souhaitez un meilleur contrôle, vous devez évaluer soigneusement ce dont vous avez besoin exactement. Certaines des méthodes typiques sont l'interpolation des tables de recherche, l'appel d'assembly (qui est souvent lent) ou d'autres schémas d'approximation tels que Newton-Raphson pour les racines carrées.


5

Si vous voulez une implémentation logicielle et non matérielle, l'endroit où chercher une réponse définitive à cette question est le chapitre 5 des recettes numériques . Ma copie est dans une boîte, donc je ne peux pas donner de détails, mais la version courte (si je me souviens bien) est que vous prenez tan(theta/2)comme opération primitive et calculez les autres à partir de là. Le calcul se fait avec une approximation série, mais c'est quelque chose qui converge beaucoup plus rapidement qu'une série Taylor.

Désolé, je ne peux plus me souvenir sans mettre la main sur le livre.


5

Il n'y a rien de tel que de frapper la source et de voir comment quelqu'un l'a fait dans une bibliothèque d'usage courant; regardons une implémentation d'une bibliothèque C en particulier. J'ai choisi uLibC.

Voici la fonction sin:

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

qui semble gérer quelques cas spéciaux, puis effectue une réduction d'argument pour mapper l'entrée à la plage [-pi / 4, pi / 4], (en divisant l'argument en deux parties, une grande partie et une queue) avant d'appeler

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

qui opère ensuite sur ces deux parties. S'il n'y a pas de queue, une réponse approximative est générée en utilisant un polynôme de degré 13. S'il y a une queue, vous obtenez un petit ajout correctif basé sur le principe quesin(x+y) = sin(x) + sin'(x')y


4

Chaque fois qu'une telle fonction est évaluée, à un certain niveau, il y a très probablement soit:

  • Un tableau de valeurs qui est interpolé (pour les applications rapides et inexactes - par exemple l'infographie)
  • L'évaluation d'une série qui converge vers la valeur souhaitée --- probablement pas une série taylor, plus probablement quelque chose basé sur une quadrature fantaisie comme Clenshaw-Curtis.

S'il n'y a pas de support matériel, le compilateur utilise probablement cette dernière méthode, émettant uniquement du code assembleur (sans symboles de débogage), plutôt que d'utiliser une bibliothèque ac --- ce qui rend difficile le suivi du code réel dans votre débogueur.


4

Comme de nombreuses personnes l'ont souligné, cela dépend de la mise en œuvre. Mais pour autant que je comprends votre question, vous étiez intéressé par une véritable implémentation logicielle des fonctions mathématiques, mais vous n'avez tout simplement pas réussi à en trouver une. Si tel est le cas, alors vous êtes ici:

  • Téléchargez le code source de la glibc depuis http://ftp.gnu.org/gnu/glibc/
  • Regardez le fichier dosincos.csitué dans le dossier racine \ sysdeps \ ieee754 \ dbl-64 de la glibc décompressée
  • De même, vous pouvez trouver des implémentations du reste de la bibliothèque mathématique, recherchez simplement le fichier avec le nom approprié

Vous pouvez également consulter les fichiers avec l' .tblextension, leur contenu n'est rien de plus que d'énormes tableaux de valeurs précalculées de différentes fonctions sous forme binaire. C'est pourquoi l'implémentation est si rapide: au lieu de calculer tous les coefficients de la série qu'ils utilisent, ils effectuent simplement une recherche rapide, ce qui est beaucoup plus rapide. BTW, ils utilisent la série Tailor pour calculer le sinus et le cosinus.

J'espère que ça aide.


4

Je vais essayer de répondre au cas d' sin()un programme C, compilé avec le compilateur C de GCC sur un processeur x86 actuel (disons un Intel Core 2 Duo).

Dans le langage C, la bibliothèque C standard comprend des fonctions mathématiques courantes, non incluses dans le langage lui-même (par exemple pow, sinet cospour la puissance, le sinus et le cosinus respectivement). Les en- têtes qui sont inclus dans math.h .

Désormais sur un système GNU / Linux, ces fonctions de bibliothèques sont fournies par glibc (GNU libc ou GNU C Library). Mais le compilateur GCC veut que vous établissiez un lien vers la bibliothèque mathématique ( libm.so) en utilisant l' -lmindicateur du compilateur pour permettre l'utilisation de ces fonctions mathématiques. Je ne sais pas pourquoi il ne fait pas partie de la bibliothèque C standard. Ce serait une version logicielle des fonctions à virgule flottante, ou "soft-float".

En plus: La raison pour laquelle les fonctions mathématiques sont séparées est historique et visait simplement à réduire la taille des programmes exécutables dans les très anciens systèmes Unix, peut-être avant que les bibliothèques partagées ne soient disponibles, à ma connaissance.

Maintenant, le compilateur peut optimiser la fonction de bibliothèque C standard sin()(fournie par libm.so) à remplacer par un appel à une instruction native vers la fonction sin () intégrée de votre CPU / FPU, qui existe en tant qu'instruction FPU ( FSINpour x86 / x87) sur des processeurs plus récents comme la série Core 2 (c'est correct à peu près aussi loin que le i486DX). Cela dépendrait des drapeaux d'optimisation passés au compilateur gcc. Si le compilateur était invité à écrire du code qui s'exécuterait sur n'importe quel processeur i386 ou plus récent, il ne ferait pas une telle optimisation. Le -mcpu=486drapeau informait le compilateur qu'il était sûr de faire une telle optimisation.

Maintenant, si le programme exécutait la version logicielle de la fonction sin (), il le ferait sur la base d'un algorithme CORDIC (COordinate Rotation DIgital Computer) ou BKM , ou plus probablement d'un tableau ou d'un calcul de série de puissance qui est couramment utilisé maintenant pour calculer ces fonctions transcendantales. [Src: http://en.wikipedia.org/wiki/Cordic#Application]

Toute version récente (depuis 2.9x environ) de gcc propose également une version intégrée de sin, __builtin_sin()qui sera utilisée pour remplacer l'appel standard à la version de la bibliothèque C, comme une optimisation.

Je suis sûr que c'est aussi clair que de la boue, mais j'espère que cela vous donnera plus d'informations que vous attendiez, et beaucoup de points de saut pour en savoir plus vous-même.



3

N'utilisez pas la série Taylor. Les polynômes de Chebyshev sont à la fois plus rapides et plus précis, comme l'ont souligné quelques personnes ci-dessus. Voici une implémentation (à l'origine de la ROM ZX Spectrum): https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
Cela ne semble pas vraiment répondre à la question posée. L'OP demande comment les fonctions trigonométriques sont calculées par les compilateurs / bibliothèques C courants (et je suis presque sûr que ZX Spectrum n'est pas qualifié), pas comment elles doivent être calculées. Cela aurait peut-être été un commentaire utile sur certaines des réponses précédentes, cependant.
Ilmari Karonen

1
Ah, tu as raison. Cela aurait dû être un commentaire et non une réponse. Je n'ai pas utilisé SO depuis un moment et j'ai oublié comment fonctionne le système. Quoi qu'il en soit, je pense que la mise en œuvre de Spectrum est pertinente car elle avait un processeur très lent et la vitesse était essentielle. Le meilleur algorithme est alors encore assez bon, donc ce serait une bonne idée pour les bibliothèques C d'implémenter des fonctions trigonométriques en utilisant les polynômes de Chebyshev.
Albert Veli

2

Le calcul sinus / cosinus / tangente est en fait très facile à faire grâce au code utilisant la série Taylor. En écrire un vous-même prend environ 5 secondes.

L'ensemble du processus peut être résumé avec cette équation ici:

le péché et l'expansion des coûts

Voici quelques routines que j'ai écrites pour 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
C'est une implémentation plutôt mauvaise car elle n'utilise pas que les termes successifs des séries sinus et cosinus ont des quotients très simples. Ce qui signifie que l'on peut réduire le nombre de multiplications et de divisions de O (n ^ 2) ici à O (n). D'autres réductions sont obtenues en divisant par deux et par quadrature comme cela se fait par exemple dans la bibliothèque mathématique bc (calculatrice multi-précision POSIX).
Lutz Lehmann

2
Il ne semble pas non plus répondre à la question telle que posée; l'OP demande comment les fonctions trigonométriques sont calculées par les compilateurs / bibliothèques C courants, et non pour les réimplémentations personnalisées.
Ilmari Karonen

2
Je pense que c'est une bonne réponse car elle répond à l'esprit de la question qui (et je ne peux que deviner bien sûr) la curiosité d'une fonction autrement "boîte noire" comme le péché (). C'est la seule réponse ici qui donne une chance de comprendre rapidement ce qui se passe en le survolant en quelques secondes plutôt qu'en lisant du code source C optimisé.
Mike M

en fait, les bibliothèques utilisent la version beaucoup plus optimisée, en se rendant compte qu'une fois que vous avez un terme, vous pouvez obtenir le terme suivant en multipliant certaines valeurs. Voir un exemple dans la réponse de Blindy . Vous calculez la puissance et les factorielles encore et encore, ce qui est beaucoup plus lent
phuclv


0

Version améliorée du code de la réponse de 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'essentiel de la façon dont cela se fait réside dans cet extrait de l'analyse numérique appliquée de Gerald Wheatley:

Lorsque votre logiciel demande à l'ordinateur d'obtenir une valeur de entrez la description de l'image iciou entrez la description de l'image ici, vous êtes-vous demandé comment il peut obtenir les valeurs si les fonctions les plus puissantes qu'il peut calculer sont des polynômes? Il ne les recherche pas dans les tableaux et ne les interpole pas! Au contraire, l'ordinateur rapproche chaque fonction autre que les polynômes d'un polynôme qui est conçu pour donner les valeurs de façon très précise.

Quelques points à mentionner sur ce qui précède est que certains algorithmes ne font en fait qu'interpoler à partir d'une table, mais seulement pour les premières itérations. Notez également comment il mentionne que les ordinateurs utilisent des polynômes d'approximation sans spécifier quel type de polynôme d'approximation. Comme d'autres l'ont souligné dans le fil, les polynômes de Chebyshev sont plus efficaces que les polynômes de Taylor dans ce cas.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.