Resposta curta:
Uma especialização de pow(x, n)onde né um número natural costuma ser útil para o desempenho de tempo . Mas o genérico da biblioteca padrão pow()ainda funciona muito ( surpreendentemente! ) Bem para esse propósito e é absolutamente crítico incluir o mínimo possível na biblioteca C padrão para que possa ser o mais portátil e fácil de implementar possível. Por outro lado, isso não o impede de estar na biblioteca padrão C ++ ou no STL, que tenho certeza que ninguém está planejando usar em algum tipo de plataforma embarcada.
Agora, para a longa resposta.
pow(x, n)pode ser feito muito mais rápido em muitos casos, especializando-se npara um número natural. Tive de usar minha própria implementação dessa função para quase todos os programas que escrevo (mas escrevo muitos programas matemáticos em C). A operação especializada pode ser feita em O(log(n))tempo, mas quando né pequena, uma versão linear mais simples pode ser mais rápida. Aqui estão implementações de ambos:
// Computes x^n, where n is a natural number.
double pown(double x, unsigned n)
{
double y = 1;
// n = 2*d + r. x^n = (x^2)^d * x^r.
unsigned d = n >> 1;
unsigned r = n & 1;
double x_2_d = d == 0? 1 : pown(x*x, d);
double x_r = r == 0? 1 : x;
return x_2_d*x_r;
}
// The linear implementation.
double pown_l(double x, unsigned n)
{
double y = 1;
for (unsigned i = 0; i < n; i++)
y *= x;
return y;
}
(Saí xe o valor de retorno dobra porque o resultado de pow(double x, unsigned n)caberá em um dobro com a frequência necessária pow(double, double).)
(Sim, powné recursivo, mas quebrar a pilha é absolutamente impossível, pois o tamanho máximo da pilha será aproximadamente igual log_2(n)e né um inteiro. Se nfor um inteiro de 64 bits, isso dá a você um tamanho máximo de pilha de cerca de 64. Nenhum hardware tem tal extremo limitações de memória, exceto para alguns PICs duvidosos com pilhas de hardware que têm profundidade de 3 a 8 chamadas de função.)
Quanto ao desempenho, você ficará surpreso com o que uma variedade de jardim pow(double, double)é capaz. Testei cem milhões de iterações em meu IBM Thinkpad de 5 anos xigual ao número da iteração e nigual a 10. Nesse cenário, pown_lvenceu. glibc pow()levou 12,0 segundos do usuário, pown7,4 segundos do usuário e pown_lapenas 6,5 segundos do usuário. Portanto, isso não é muito surpreendente. Estávamos mais ou menos esperando isso.
Então, eu deixo xser constante (eu configurei para 2,5), e fiz um loop nde 0 a 19 cem milhões de vezes. Desta vez, de forma bastante inesperada, glibc powvenceu, e por um deslizamento de terra! Demorou apenas 2,0 segundos do usuário. Meu powndemorou 9,6 segundos e pown_l12,2 segundos. O que aconteceu aqui? Fiz outro teste para descobrir.
Eu fiz a mesma coisa acima apenas com xigual a um milhão. Desta vez, pownvenceu a 9.6s. pown_llevou 12,2s e glibc pow levou 16,3s. Agora está claro! glibc tem powmelhor desempenho que os três quando xestá baixo, mas pior quando xestá alto. Quando xestá alto, tem pown_lmelhor desempenho quando nestá baixo e tem pownmelhor desempenho quando xestá alto.
Portanto, aqui estão três algoritmos diferentes, cada um capaz de ter um desempenho melhor do que os outros nas circunstâncias certas. Então, em última análise, que a utilização mais provável depende de como você está pensando em usar pow, mas usando a versão correta é vale a pena, e com todas as versões é bom. Na verdade, você pode até automatizar a escolha do algoritmo com uma função como esta:
double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
if (x_expected < x_threshold)
return pow(x, n);
if (n_expected < n_threshold)
return pown_l(x, n);
return pown(x, n);
}
Contanto que x_expectede n_expectedsejam constantes decididas em tempo de compilação, junto com possivelmente algumas outras advertências, um compilador de otimização que valha a pena removerá automaticamente a pown_autochamada de função inteira e a substituirá pela escolha apropriada dos três algoritmos. (Agora, se você realmente vai tentar usar isso, provavelmente terá que brincar um pouco com isso, porque eu não tentei exatamente compilar o que escrevi acima.;))
Por outro lado, glibc pow funciona e glibc já é grande o suficiente. O padrão C deve ser portátil, incluindo para vários dispositivos incorporados (na verdade, desenvolvedores incorporados em todos os lugares geralmente concordam que glibc já é muito grande para eles), e não pode ser portátil se para cada função matemática simples ele precisa incluir todos algoritmo alternativo que pode ser útil. Então, é por isso que não está no padrão C.
nota de rodapé: no teste de desempenho de tempo, dei às minhas funções sinalizadores de otimização relativamente generosos ( -s -O2) que provavelmente são comparáveis, senão piores do que o que provavelmente foi usado para compilar glibc em meu sistema (archlinux), então os resultados são provavelmente justo. Para um teste mais rigoroso, eu teria que compilar glibc mim e eu reeeally não sentir vontade de fazer isso. Eu costumava usar o Gentoo, então me lembro quanto tempo leva, mesmo quando a tarefa é automatizada . Os resultados são conclusivos (ou melhor, inconclusivos) o suficiente para mim. É claro que você pode fazer isso sozinho.
Rodada de bônus: A especialização de pow(x, n)para todos os inteiros é instrumental se uma saída inteira exata for necessária, o que acontece. Considere alocar memória para uma matriz N-dimensional com p ^ N elementos. Tirar p ^ N igual a um resultará em um segfault que pode ocorrer aleatoriamente.