Como C calcula sin () e outras funções matemáticas?


248

Eu estive pesquisando desmontagens .NET e o código-fonte do GCC, mas parece que não consigo encontrar em lugar algum a implementação real sin()e outras funções matemáticas ... elas sempre parecem fazer referência a outra coisa.

Alguém pode me ajudar a encontrá-los? Eu sinto que é improvável que TODO o hardware em que C seja executado suporte funções trigonométricas no hardware, então deve haver um algoritmo de software em algum lugar , certo?


Estou ciente de várias maneiras pelas quais as funções podem ser calculadas e escrevi minhas próprias rotinas para calcular funções usando a série taylor por diversão. Estou curioso para saber o quão real são as linguagens de produção, pois todas as minhas implementações são sempre várias ordens de magnitude mais lentas, embora eu ache que meus algoritmos são bastante inteligentes (obviamente não são).


2
Observe que essa implementação depende. Você deve especificar qual implementação você está mais interessado.
jason

3
Marquei .NET e C porque procurei nos dois lugares e também não consegui descobrir. Embora observando a desmontagem do .NET, parece que ele pode estar chamando C não gerenciado, pelo que sei que eles têm a mesma implementação.
Hank

Respostas:


213

Na GNU libm, a implementação de sindepende do sistema. Portanto, você pode encontrar a implementação, para cada plataforma, em algum lugar no subdiretório apropriado do sysdeps .

Um diretório inclui uma implementação em C, contribuída pela IBM. Desde outubro de 2011, esse é o código que é executado quando você liga sin()para um sistema Linux x86-64 típico. Aparentemente, é mais rápido que a fsininstrução de montagem. Código-fonte: sysdeps / ieee754 / dbl-64 / s_sin.c , procure __sin (double x).

Este código é muito complexo. Nenhum algoritmo de software é o mais rápido possível e também preciso em toda a faixa de valores x ; portanto, a biblioteca implementa vários algoritmos diferentes, e seu primeiro trabalho é examinar x e decidir qual algoritmo usar.

  • Quando x é muito muito próximo de 0, sin(x) == xé a resposta certa.

  • Um pouco mais adiante, sin(x)usa a familiar série Taylor. No entanto, isso é preciso apenas próximo de 0, então ...

  • Quando o ângulo é superior a cerca de 7 °, um algoritmo diferente é usado, computando aproximações da série Taylor tanto para sin (x) quanto cos (x), usando valores de uma tabela pré-computada para refinar a aproximação.

  • Quando | x | > 2, nenhum dos algoritmos acima funcionaria; portanto, o código começa calculando algum valor mais próximo de 0 que pode ser alimentado sinou cosusado.

  • Existe ainda outro ramo para lidar com x sendo um NaN ou infinito.

Esse código usa alguns hacks numéricos que eu nunca vi antes, embora, pelo que sei, possam ser bem conhecidos entre os especialistas em ponto flutuante. Às vezes, algumas linhas de código levam vários parágrafos para explicar. Por exemplo, essas duas linhas

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

são usados ​​(algumas vezes) na redução de x para um valor próximo de 0 que difere de x por um múltiplo de π / 2, especificamente xn× π / 2. A maneira como isso é feito sem divisão ou ramificação é bastante inteligente. Mas não há nenhum comentário!


As versões mais antigas de 32 bits do GCC / glibc usavam a fsininstrução, que é surpreendentemente imprecisa para algumas entradas. Há uma postagem de blog fascinante ilustrando isso com apenas 2 linhas de código .

A implementação do fdlibm sinem C puro é muito mais simples que a do glibc e é bem comentada. Código fonte: fdlibm / s_sin.c e fdlibm / k_sin.c


35
Para ver que esse é realmente o código que roda no x86: compile um programa que chama sin(); digite gdb a.out, então break sin, então run, então disassemble.
Jason Orendorff

5
@ Henry: não cometa o erro de pensar que esse é um bom código. É realmente terrível , não aprenda a codificar dessa maneira!
Thomas Bonini

2
@ Andreas Hmm, você está certo, o código IBM parece bastante horrível em comparação com o fdlibm. Editei a resposta para adicionar links à rotina senoidal da fdlibm.
Jason Orendorff

3
@ Henry: __kernel_siné definido no k_sin.c, porém, e é puro C. Clique novamente - eu estraguei o URL pela primeira vez.
Jason Orendorff

3
O código sysdeps vinculado é particularmente interessante porque é arredondado corretamente. Ou seja, aparentemente fornece a melhor resposta possível para todos os valores de entrada, o que só se tornou possível recentemente. Em alguns casos, isso pode ser lento, pois muitos dígitos extras podem precisar ser calculados para garantir o arredondamento correto. Em outros casos, é extremamente rápido - para números pequenos o suficiente, a resposta é exatamente o ângulo.
Bruce Dawson

66

Funções como seno e cosseno são implementadas em microcódigo dentro de microprocessadores. Os chips Intel, por exemplo, têm instruções de montagem para eles. O compilador de CA gerará código que chama essas instruções de montagem. (Por outro lado, um compilador Java não avaliará. O Java avalia funções trigonométricas no software e não no hardware e, portanto, é muito mais lento.)

Os chips não usam a série Taylor para calcular funções trigonométricas, pelo menos não inteiramente. Antes de tudo, eles usam o CORDIC , mas também podem usar uma série curta de Taylor para aprimorar o resultado do CORDIC ou para casos especiais, como calcular o seno com alta precisão relativa em ângulos muito pequenos. Para mais explicações, consulte esta resposta StackOverflow .


10
funções matemáticas transcendentais como seno e cosseno podem ser implementadas em microcódigo ou como instruções de hardware nos atuais processadores de desktop e servidor de 32 bits. Esse nem sempre foi o caso, até que o i486 (DX) todos os cálculos de ponto flutuante foram feitos no software ("soft-float") da série x86 sem um coprocessador separado. Nem todos (FPUs) incluíram funções transcendentais (por exemplo, Weitek 3167).
Mctylr

1
Você pode ser mais específico? Como se "aperfeiçoa" uma aproximação usando uma série de Taylor?
Hank

4
Quanto a "aperfeiçoar" uma resposta, suponha que você esteja computando seno e cosseno. Suponha que você saiba o valor exato de ambos em um ponto (por exemplo, de CORDIC), mas deseje o valor em um ponto próximo. Então, para uma pequena diferença h, você pode aplicar as aproximações 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
Os chips x86 / x64 têm uma instrução de montagem para calcular seno (fsin), mas essa instrução às vezes é bastante imprecisa e, portanto, raramente é mais usada. Veja randomascii.wordpress.com/2014/10/09/… para obter detalhes. A maioria dos outros processadores não possui instruções para seno e cosseno, pois calculá-los em software oferece mais flexibilidade e pode até ser mais rápido.
Bruce Dawson

3
As coisas cordiais dentro dos chips intel geralmente não são usadas. Primeiro, a precisão e a resolução da operação são extremamente importantes para muitas aplicações. O Cordic é notoriamente impreciso quando você chega ao sétimo dígito, e é imprevisível. Em segundo lugar, ouvi dizer que há um bug em sua implementação, o que causa ainda mais problemas. Dei uma olhada na função sin do linux gcc e, com certeza, ela usa chebyshev. o material embutido não é usado. Ah, também, o algoritmo cordial no chip é mais lento que a solução de software.
Donald Murray

63

OK, crianças, hora dos profissionais .... Essa é uma das minhas maiores reclamações com engenheiros de software inexperientes. Eles vêm calculando funções transcendentais do zero (usando a série de Taylor) como se ninguém tivesse feito esses cálculos antes em suas vidas. Não é verdade. Esse é um problema bem definido e foi abordado milhares de vezes por engenheiros muito inteligentes de software e hardware e tem uma solução bem definida. Basicamente, a maioria das funções transcendentais usa os polinômios de Chebyshev para calculá-los. O uso de polinômios depende das circunstâncias. Primeiro, a Bíblia sobre esse assunto é um livro chamado "Computer Approximations", de Hart e Cheney. Nesse livro, você pode decidir se possui um somador, multiplicador, divisor etc., e decide quais operações são mais rápidas. Por exemplo, se você tivesse um divisor muito rápido, a maneira mais rápida de calcular seno pode ser P1 (x) / P2 (x) em que P1, P2 são polinômios de Chebyshev. Sem o divisor rápido, pode ser apenas P (x), onde P tem muito mais termos do que P1 ou P2 .... então seria mais lento. Portanto, o primeiro passo é determinar o seu hardware e o que ele pode fazer. Em seguida, você escolhe a combinação apropriada de polinômios de Chebyshev (geralmente é da forma cos (ax) = aP (x) para cosseno, por exemplo, novamente onde P é um polinômio de Chebyshev). Então você decide qual precisão decimal deseja. por exemplo, se você deseja uma precisão de 7 dígitos, procure na tabela apropriada do livro que mencionei e ele fornecerá (para precisão = 7,33) um número N = 4 e um número polinomial 3502. N é a ordem do polinômio (então é p4.x ^ 4 + p3.x ^ 3 + p2.x ^ 2 + p1.x + p0), porque N = 4. Então você procura o valor real de p4, p3, p2, p1, valores de p0 na parte de trás do livro em 3502 (eles estarão em ponto flutuante). Em seguida, você implementa seu algoritmo em software na forma: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... e é assim que você calcula o cosseno com 7 decimais lugares nesse hardware.

Observe que a maioria das implementações de hardware de operações transcendentais em uma FPU geralmente envolve alguns microcódigos e operações como essa (depende do hardware). Os polinômios de Chebyshev são usados ​​para a maioria dos transcendentais, mas não todos. Por exemplo, a raiz quadrada é mais rápida para usar uma iteração dupla do método Newton Raphson usando uma tabela de pesquisa primeiro. Mais uma vez, o livro "Aproximações por Computador" dirá isso.

Se você planeja implementar essas funções, recomendo a todos que obtenham uma cópia desse livro. É realmente a Bíblia para esses tipos de algoritmos. Observe que existem vários meios alternativos para calcular esses valores, como cordics, etc., mas esses tendem a ser melhores para algoritmos específicos, nos quais você só precisa de baixa precisão. Para garantir sempre a precisão, os polinômios chebyshev são o caminho a seguir. Como eu disse, problema bem definido. Foi resolvido há 50 anos ... e é assim que é feito.

Agora, dito isso, existem técnicas pelas quais os polinômios de Chebyshev podem ser usados ​​para obter um único resultado de precisão com um polinômio de baixo grau (como o exemplo do cosseno acima). Em seguida, existem outras técnicas para interpolar entre valores para aumentar a precisão sem precisar ir para um polinômio muito maior, como o "Método de tabelas precisas de Gal". Esta última técnica é a que se refere o post referente à literatura da ACM. Mas, em última análise, os polinômios de Chebyshev são o que é usado para obter 90% do caminho até lá.

Aproveitar.


6
Eu não poderia concordar mais com as primeiras frases. Além disso, vale lembrar que calcular funções especiais com precisão garantida é um problema difícil . As pessoas inteligentes que você menciona passam a maior parte da vida fazendo isso. Além disso, em uma nota mais técnica, os polinômios min-max são os mais procurados, e os polinômios Chebyshev são proxies mais simples para eles.
Alexandre C.

161
-1 para o tom não profissional e divagante (e levemente rude) e para o fato de que o conteúdo não redundante real dessa resposta, despido da divagação e condescendência, basicamente se resume a "Eles costumam usar polinômios de Chebyshev; consulte este livro para mais detalhes, é muito bom! " O que, você sabe, pode estar absolutamente correto, mas não é realmente o tipo de resposta independente que queremos aqui no SO. Condensado assim, teria feito um comentário decente sobre a questão.
Ilmari Karonen

2
Nos primeiros anos de desenvolvimento de jogos, isso geralmente era feito com tabelas de pesquisa, com necessidade crítica de velocidade). Normalmente, não usamos as funções lib padrão para essas coisas.
topspin

4
Uso tabelas de pesquisa em sistemas embarcados com bastante frequência e bittians (em vez de radianos), mas isso é para um aplicativo especializado (como seus jogos). Eu acho que o cara está interessado em como o compilador C calcula pecado para números de ponto flutuante ....
Donald Murray

1
Ah, há 50 anos. Comecei a brincar com isso no Burroughs B220 da série McLaren. Mais tarde, o hardware do CDC e o Motorola 68000. O Arcsin estava confuso - escolhi o quociente de dois polinômios e desenvolvi o código para encontrar os coeficientes ideais.
Rick James

15

Para sinespecificamente, o uso da expansão Taylor daria a você:

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

você continuaria adicionando termos até que a diferença entre eles seja menor que um nível de tolerância aceito ou apenas para uma quantidade finita de etapas (mais rápidas, mas menos precisas). Um exemplo seria algo como:

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) funciona devido à aproximação sin (x) = x para pequenos ângulos. Para ângulos maiores, é necessário calcular cada vez mais termos para obter resultados aceitáveis. Você pode usar um argumento while e continuar com uma certa precisão:

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 você ajustar um pouco os coeficientes (e codificá-los em um polinômio), poderá interromper duas iterações mais cedo.
Rick James

14

Sim, também existem algoritmos de software para cálculo sin. Basicamente, o cálculo desse tipo de coisa com um computador digital geralmente é feito usando métodos numéricos como aproximar a série de Taylor representa a função.

Os métodos numéricos podem aproximar as funções a uma quantidade arbitrária de precisão e, como a quantidade de precisão que você tem em um número flutuante é finita, eles se adaptam bem a essas tarefas.


12
Uma implementação real provavelmente não usará uma série de Taylor, pois existem maneiras mais eficientes. Você só precisa se aproximar corretamente no domínio [0 ... pi / 2], e há funções que fornecerão uma boa aproximação com mais eficiência do que uma série de Taylor.
David Thornley

2
@ David: Eu concordo. Tomei o cuidado de mencionar a palavra "curtir" na minha resposta. Mas a expansão de Taylor é simples para explicar a idéia por trás de métodos que aproximam funções. Dito isto, vi implementações de software (não tenho certeza se foram otimizadas) que usavam a série Taylor.
Mehrdad Afshari

1
Na verdade, as aproximações polinomiais são uma das maneiras mais eficientes de calcular funções trigonométricas.
Jeremy Salwen

13

Use a série Taylor e tente encontrar uma relação entre os termos da série para não calcular as coisas repetidamente

Aqui está um exemplo para 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;
}

usando isso, podemos obter o novo termo da soma usando o já usado (evitamos o fatorial ex 2p )

explicação


2
Você sabia que pode usar a API do Google Chart para criar fórmulas como essa usando o TeX? code.google.com/apis/chart/docs/gallery/formulas.html
Gab Royer em

11

É uma pergunta complexa. A CPU semelhante à Intel da família x86 possui uma implementação de hardware da sin()função, mas faz parte da FPU x87 e não é mais usada no modo de 64 bits (onde os registros SSE2 são usados). Nesse modo, uma implementação de software é usada.

Existem várias implementações desse tipo por aí. Um está no fdlibm e é usado em Java. Até onde eu sei, a implementação glibc contém partes do fdlibm e outras partes contribuídas pela IBM.

Implementações de software de funções transcendentais, como sin()normalmente usam aproximações por polinômios, geralmente obtidas na série Taylor.


3
Os registros SSE2 não são usados ​​para calcular sin (), nem no modo x86 nem no x64 e, é claro, o pecado é calculado no hardware, independentemente do modo. Ei, é 2010 em que vivemos :)
Igor Korkhov

7
@ Igor: isso depende da biblioteca de matemática que você está procurando. Acontece que as bibliotecas matemáticas mais otimizadas do x86 usam implementações de software SSE sine cossão mais rápidas que as instruções de hardware na FPU. Bibliotecas mais simples e ingênuas tendem a usar as instruções fsine fcos.
Stephen Canon

@ Stephen Canon: Essas bibliotecas rápidas têm precisão de 80 bits, como os registros FPU? Eu tenho uma suspeita muito sorrateira de que eles preferem a velocidade à precisão, o que obviamente é razoável em muitos cenários, por exemplo em jogos. E acredito que calcular o seno com precisão de 32 bits usando SSE e tabelas intermediárias pré-computadas pode ser mais rápido do que usando FSINcom precisão total. Ficaria muito grato se você me disser os nomes dessas bibliotecas rápidas, é interessante dar uma olhada.
Igor Korkhov

@ Igor: no x86 no modo de 64 bits, pelo menos em todos os sistemas do tipo Unix que conheço, a precisão é limitada a 64 bits, não aos 79 bits da x87 FPU. A implementação de software sin()acontece cerca de duas vezes mais rápido que o fsincalculado (justamente porque é feito com menos precisão). Observe que o x87 é conhecido por ter um pouco menos de precisão real do que os anunciados 79 bits.
Thomas Pornin

1
De fato, as implementações de sin () de 32 bits e 64 bits nas bibliotecas de tempo de execução msvc não usam a instrução FSIN. De fato, eles dão resultados diferentes, como por exemplo o pecado (0.70444454416678126). Isso resultará em 0,64761068800896837 (à direita com tolerância de 0,5 * (eps / 2)) em um programa de 32 bits e resultará em 0,64761068800896848 (incorreto) em um de 64 bits.
precisa saber é

9

Os polinômios de Chebyshev, como mencionado em outra resposta, são os polinômios em que a maior diferença entre a função e o polinômio é a menor possível. Esse é um excelente começo.

Em alguns casos, o erro máximo não é o que você está interessado, mas o erro relativo máximo. Por exemplo, para a função seno, o erro próximo de x = 0 deve ser muito menor do que para valores maiores; você quer um pequeno erro relativo . Portanto, você calcularia o polinômio de Chebyshev para sin x / x e multiplicaria esse polinômio por x.

Em seguida, você precisa descobrir como avaliar o polinômio. Você deseja avaliá-lo de forma que os valores intermediários sejam pequenos e, portanto, os erros de arredondamento sejam pequenos. Caso contrário, os erros de arredondamento podem se tornar muito maiores que os erros no polinômio. E com funções como a função seno, se você for descuidado, pode ser possível que o resultado que você calcule para o pecado x seja maior que o resultado para o pecado y mesmo quando x <y. Portanto, é necessária uma escolha cuidadosa da ordem de cálculo e do cálculo dos limites superiores para o erro de arredondamento.

Por exemplo, sen x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/5040 ... Se você calcular ingenuamente sin x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - x ^ 6/5040 ...), então essa função em parênteses está diminuindo, e isso vai acontecer que, se y é o próximo número maior para x, então às vezes pecam y será menor do que o pecado x. Em vez disso, calcule sin x = x - x ^ 3 * (1/6 - x ^ 2/120 + x ^ 4/5040 ...) onde isso não pode acontecer.

Ao calcular polinômios de Chebyshev, você geralmente precisa arredondar os coeficientes para dobrar a precisão, por exemplo. Mas enquanto um polinômio de Chebyshev é ideal, o polinômio de Chebyshev com coeficientes arredondados para precisão dupla não é o polinômio ideal com coeficientes de precisão dupla!

Por exemplo, para sin (x), onde você precisa de coeficientes para x, x ^ 3, x ^ 5, x ^ 7 etc., faça o seguinte: Calcule a melhor aproximação do pecado x com um polinômio (ax + bx ^ 3 + cx ^ 5 + dx ^ 7) com precisão maior que o dobro, depois arredonde a para precisão dupla, dando A. A diferença entre a e A seria muito grande. Agora calcule a melhor aproximação de (sin x - Ax) com um polinômio (bx ^ 3 + cx ^ 5 + dx ^ 7). Você obtém coeficientes diferentes, porque eles se adaptam à diferença entre a e A. Arredonde b para dobrar a precisão B. Em seguida, aproxime (sen x - Ax - Bx ^ 3) com um polinômio cx ^ 5 + dx ^ 7 e assim por diante. Você obterá um polinômio quase tão bom quanto o polinômio Chebyshev original, mas muito melhor que o Chebyshev arredondado para dobrar a precisão.

Em seguida, você deve levar em consideração os erros de arredondamento na escolha do polinômio. Você encontrou um polinômio com erro mínimo no polinômio que ignora o erro de arredondamento, mas deseja otimizar o polinômio mais o erro de arredondamento. Depois de obter o polinômio Chebyshev, você pode calcular os limites para o erro de arredondamento. Digamos que f (x) é sua função, P (x) é o polinômio e E (x) é o erro de arredondamento. Você não quer otimizar | f (x) - P (x) |, você deseja otimizar | f (x) - P (x) +/- E (x) | Você obterá um polinômio ligeiramente diferente que tenta manter os erros polinomiais baixos onde o erro de arredondamento é grande e relaxa os erros polinomiais um pouco, onde o erro de arredondamento é pequeno.

Tudo isso fará com que você facilmente arredonde erros de no máximo 0,55 vezes o último bit, onde +, -, *, / tenha erros de arredondamento de no máximo 0,50 vezes o último bit.


1
Esta é uma boa explicação de como se pode calcular sin (x) de forma eficiente, mas ele realmente não parecem responder à pergunta do OP, que é especificamente sobre quão comum bibliotecas C / compiladores que calculá-lo.
Ilmari Karonen

Os polinômios de Chebyshev minimizam o valor absoluto máximo em um intervalo, mas não minimizam a maior diferença entre uma função de destino e o polinômio. Os polinômios Minimax fazem isso.
Eric Postpischil

9

No que se refere função trigonométrica como sin(), cos(), tan()não houve nenhuma menção, depois de 5 anos, de um aspecto importante de funções trigonométricas alta qualidade: redução Gama .

Um passo inicial em qualquer uma dessas funções é reduzir o ângulo, em radianos, para um intervalo de 2 * π. Mas π é irracional, de modo que reduções simples como x = remainder(x, 2*M_PI)introduzir erro como M_PIou pi de máquina são uma aproximação de π. Então, como fazer x = remainder(x, 2*π)?

As bibliotecas antigas usavam precisão estendida ou programação criada para fornecer resultados de qualidade, mas ainda em um intervalo limitado de double. Quando um valor grande era solicitado sin(pow(2,30)), os resultados eram sem sentido ou 0.0e talvez com um sinalizador de erro definido como algo como TLOSSperda total de precisão ou PLOSSperda parcial de precisão.

Uma boa redução de valores grandes para um intervalo como -π a π é um problema desafiador que rivaliza com os desafios da função trigonométrica básica, como sin()ela própria.

Um bom relatório é a redução de argumentos para grandes argumentos: bom até o último bit (1992). Ele abrange a questão assim: discute a necessidade e como as coisas eram em várias plataformas (SPARC, PC, HP, 30+ outro) e fornece um algoritmo de solução a dá resultados de qualidade para todos double a partir -DBL_MAXde DBL_MAX.


Se os argumentos originais estiverem em graus, ainda que possam ter um valor grande, use fmod()primeiro para obter maior precisão. Uma mercadoria fmod()não apresentará erros e, portanto, proporcionará uma excelente redução de alcance.

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

Várias identidades trigonométricas e remquo()oferecem ainda mais melhorias. Amostra: sind ()


6

A implementação real das funções da biblioteca depende do compilador e / ou provedor da biblioteca específicos. Seja em hardware ou software, seja uma expansão de Taylor ou não, etc., variará.

Sei que isso não ajuda em nada.


5

Eles geralmente são implementados em software e não usam as chamadas de hardware correspondentes (isto é, montagem) na maioria dos casos. No entanto, como Jason apontou, estes são específicos da implementação.

Observe que essas rotinas de software não fazem parte das fontes do compilador, mas serão encontradas na biblioteca de correspodificação como o clib ou glibc para o compilador GNU. Consulte http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

Se você deseja maior controle, avalie cuidadosamente o que precisa exatamente. Alguns dos métodos típicos são a interpolação de tabelas de consulta, a chamada de montagem (que geralmente é lenta) ou outros esquemas de aproximação, como Newton-Raphson, para raízes quadradas.


5

Se você deseja uma implementação em software, não em hardware, o lugar para procurar uma resposta definitiva para essa pergunta é o Capítulo 5 de Receitas numéricas . Minha cópia está em uma caixa, então não posso dar detalhes, mas a versão curta (se bem me lembro) é que você toma tan(theta/2)como sua operação primitiva e calcula as outras a partir daí. O cálculo é feito com uma aproximação de série, mas é algo que converge muito mais rapidamente do que uma série de Taylor.

Desculpe, não posso me lembrar mais sem colocar minha mão no livro.


5

Não há nada como acessar a fonte e ver como alguém realmente fez isso em uma biblioteca de uso comum; vejamos uma implementação de biblioteca C em particular. Eu escolhi o uLibC.

Aqui está a função sin:

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

que parece lidar com alguns casos especiais e, em seguida, realiza alguma redução de argumento para mapear a entrada para o intervalo [-pi / 4, pi / 4] (dividindo o argumento em duas partes, uma grande parte e uma cauda) antes de ligar

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

que então opera nessas duas partes. Se não houver cauda, ​​uma resposta aproximada será gerada usando um polinômio de grau 13. Se houver uma cauda, ​​você receberá uma pequena adição corretiva com base no princípio de quesin(x+y) = sin(x) + sin'(x')y


4

Sempre que essa função é avaliada, em algum nível, é mais provável:

  • Uma tabela de valores que é interpolada (para aplicativos rápidos e imprecisos - por exemplo, gráficos de computador)
  • A avaliação de uma série que converge para o valor desejado - provavelmente não uma série de Taylor, mais provavelmente algo baseado em uma quadratura extravagante como Clenshaw-Curtis.

Se não houver suporte de hardware, o compilador provavelmente usará o último método, emitindo apenas código do assembler (sem símbolos de depuração), em vez de usar a biblioteca ac - tornando complicado rastrear o código real no depurador.


4

Como muitas pessoas apontaram, isso depende da implementação. Mas, pelo que entendi, você estava interessado em uma verdadeira implementação software das funções matemáticas, mas simplesmente não conseguiu encontrar uma. Se for esse o caso, aqui está você:

  • Faça o download do código-fonte glibc em http://ftp.gnu.org/gnu/glibc/
  • Veja o arquivo dosincos.clocalizado em pasta glibc root \ sysdeps \ ieee754 \ dbl-64 descompactada
  • Da mesma forma, você pode encontrar implementações do restante da biblioteca matemática, basta procurar o arquivo com o nome apropriado

Você também pode dar uma olhada nos arquivos com a .tblextensão, seu conteúdo nada mais é do que enormes tabelas de valores pré-computados de diferentes funções em uma forma binária. É por isso que a implementação é tão rápida: em vez de calcular todos os coeficientes de qualquer série que eles usem, eles fazem uma pesquisa rápida, que é muito mais rápida. Aliás, eles usam a série Tailor para calcular seno e cosseno.

Eu espero que isso ajude.


4

Vou tentar responder pelo caso de sin()um programa C, compilado com o compilador C do GCC em um processador x86 atual (digamos, um Intel Core 2 Duo).

Na linguagem C, a Biblioteca C padrão inclui funções matemáticas comuns, não incluídas na própria linguagem (por exemplo pow, sine cospara poder, seno e cosseno, respectivamente). Os cabeçalhos estão incluídos no math.h .

Agora em um sistema GNU / Linux, essas funções de bibliotecas são fornecidas pelo glibc (GNU libc ou GNU C Library). Mas o compilador GCC deseja que você vincule à biblioteca matemática ( libm.so) usando o -lmsinalizador do compilador para permitir o uso dessas funções matemáticas. Não sei por que não faz parte da biblioteca C padrão. Essa seria uma versão de software das funções de ponto flutuante ou "flutuação suave".

Além disso: O motivo para separar as funções matemáticas é histórico e pretendia apenas reduzir o tamanho dos programas executáveis ​​em muito sistemas Unix antigos, possivelmente antes que as bibliotecas compartilhadas estivessem disponíveis, até onde eu saiba.

Agora, o compilador pode otimizar a função da biblioteca C padrão sin()(fornecida por libm.so) para ser substituída por uma chamada para uma instrução nativa da função sin () interna da CPU / FPU, que existe como uma instrução FPU ( FSINpara x86 / x87) em processadores mais recentes, como a série Core 2 (isso está correto desde o i486DX). Isso dependeria dos sinalizadores de otimização passados ​​para o compilador gcc. Se o compilador fosse instruído a escrever o código que seria executado em qualquer processador i386 ou mais recente, isso não faria tal otimização. O -mcpu=486sinalizador informaria ao compilador que era seguro fazer essa otimização.

Agora, se o programa executasse a versão do software da função sin (), o faria com base no algoritmo CORDIC (Computador de Coordenação de Rotação Coordenada) ou BKM , ou mais provavelmente em um cálculo de tabela ou série de potências que é usado agora para calcular tais funções transcendentais. [Src: http://en.wikipedia.org/wiki/Cordic#Application]

Qualquer versão recente (desde 2,9x aprox.) Do gcc também oferece uma versão interna do sin, __builtin_sin()que será usada para substituir a chamada padrão para a versão da biblioteca C, como uma otimização.

Tenho certeza de que isso é tão claro quanto a lama, mas espero que lhe dê mais informações do que você esperava e muitos pontos de partida para aprender mais.



3

Não use a série Taylor. Os polinômios de Chebyshev são mais rápidos e precisos, conforme apontado por algumas pessoas acima. Aqui está uma implementação (originalmente da ROM do ZX Spectrum): https://albertveli.wordpress.com/2015/01/10/zx-sine/


2
Isso realmente não parece responder à pergunta conforme solicitado. O OP está perguntando como as funções trigonométricas são calculadas por compiladores / bibliotecas C comuns (e eu tenho certeza que o ZX Spectrum não se qualifica), não como elas devem ser calculadas. Esse pode ter sido um comentário útil sobre algumas das respostas anteriores.
Ilmari Karonen

1
Ah, você está certo. Deveria ter sido um comentário e não uma resposta. Eu não uso o SO há algum tempo e esqueci como o sistema funciona. Enfim, acho que a implementação do Spectrum é relevante porque tinha uma CPU muito lenta e a velocidade era essencial. O melhor algoritmo então ainda é certamente muito bom, portanto, seria uma boa idéia para as bibliotecas C implementar funções trigonométricas usando polinômios Chebyshev.
Albert Veli

2

Computar seno / cosseno / tangente é realmente muito fácil de fazer através do código usando a série Taylor. Escrever um você mesmo leva cerca de 5 segundos.

Todo o processo pode ser resumido com esta equação aqui:

expansão de pecado e custo

Aqui estão algumas rotinas que escrevi para 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
Essa é uma implementação bastante ruim, uma vez que não se usa que os termos sucessivos das séries seno e cosseno tenham quocientes muito simples. O que significa que é possível reduzir o número de multiplicações e divisões de O (n ^ 2) aqui para O (n). Reduções adicionais são obtidas pela metade e ao quadrado, como, por exemplo, na biblioteca de matemática bc (calculadora multiprecisão POSIX).
Lutz Lehmann

2
Também não parece estar respondendo à pergunta, conforme solicitado; o OP está perguntando como as funções trigonométricas são calculadas por compiladores / bibliotecas C comuns, não por reimplementações personalizadas.
Ilmari Karonen

2
Eu acho que é uma boa resposta, pois responde ao espírito da pergunta que (e só posso adivinhar, é claro) a curiosidade sobre uma outra "caixa preta" que funciona como sin (). É a única resposta aqui que nos dá a chance de entender rapidamente o que está acontecendo, encobrindo-o em alguns segundos, em vez de ler algum código-fonte C otimizado.
Mike M

de fato, as bibliotecas usam a versão muito mais otimizada, ao perceber que, uma vez que você tenha um termo, você pode obter o próximo termo multiplicando alguns valores. Veja um exemplo na resposta de Blindy . Você está calculando o poder e fatoriais uma e outra vez que é muito mais lento
phuclv


0

Versão aprimorada do código da resposta 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

A essência de como isso ocorre está no trecho da Análise Numérica Aplicada de Gerald Wheatley:

Quando o seu programa de software pede ao computador para obter um valor de insira a descrição da imagem aquiou insira a descrição da imagem aqui, você já se perguntou como ele pode obter os valores se as funções mais poderosas que ele pode calcular são polinômios? Ele não os consulta nas tabelas e interpola! Em vez disso, o computador aproxima todas as funções, exceto polinômios, de algum polinômio personalizado para fornecer os valores com muita precisão.

Alguns pontos a serem mencionados acima são que alguns algoritmos interpolam a partir de uma tabela, embora apenas nas primeiras iterações. Observe também como ele menciona que os computadores utilizam polinômios aproximados sem especificar qual tipo de polinômio aproximado. Como outros membros do segmento apontaram, os polinômios de Chebyshev são mais eficientes do que os polinômios de Taylor nesse caso.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.