Eu sinto que devo simplesmente ser incapaz de encontrar. Existe alguma razão para que a pow
função C ++ não implemente a função "power" para nada, exceto float
s e double
s?
Eu sei que a implementação é trivial, apenas sinto que estou fazendo um trabalho que deveria estar em uma biblioteca padrão. Uma função de potência robusta (ou seja, lida com o estouro de alguma forma consistente e explícita) não é divertida de escrever.
double pow(int base, int exponent)
desde C ++ 11 (§26.8 [c.math] / 11 ponto 2)Respostas:
A partir de
C++11
, casos especiais foram adicionados ao conjunto de funções de energia (e outros).C++11 [c.math] /11
estados, depois de listar todas asfloat/double/long double
sobrecargas (grifo meu, e parafraseado):Portanto, basicamente, os parâmetros inteiros serão atualizados para duplos para realizar a operação.
Antes
C++11
(quando sua pergunta foi feita), não existiam sobrecargas de inteiros.Desde que eu estava nem intimamente associada com os criadores
C
, nemC++
nos dias de sua criação (embora eu estou bastante antigo), nem parte dos comitês ANSI / ISO que criaram os padrões, este é necessariamente opinião da minha parte. Eu gostaria de pensar que é uma opinião informada , mas, como minha esposa lhe dirá (freqüentemente e sem muito incentivo necessário), eu estive errado antes :-)A suposição, pelo que vale, segue.
Eu suspeito que a razão do original pré-ANSI
C
não têm esta característica é porque era totalmente desnecessário. Em primeiro lugar, já havia uma maneira perfeitamente boa de fazer potências de inteiros (com duplas e simplesmente converter de volta para um inteiro, verificando se há estouro e estouro negativo antes da conversão).Em segundo lugar, outra coisa que você deve lembrar é que a intenção original do
C
era ser uma linguagem de programação de sistemas , e é questionável se o ponto flutuante é desejável nessa área.Já que um de seus casos de uso inicial era codificar o UNIX, o ponto flutuante seria quase inútil. O BCPL, no qual o C foi baseado, também não tinha uso para potências (não tinha ponto flutuante, de memória).
Terceiro, como a implementação do poder integral é relativamente trivial, é quase certo que os desenvolvedores da linguagem usariam melhor seu tempo fornecendo coisas mais úteis (veja abaixo os comentários sobre o custo de oportunidade).
Isso também é relevante para o original
C++
. Como a implementação original era efetivamente apenas um tradutor que produziaC
código, ela carregava muitos dos atributos deC
. Sua intenção original era C-com-classes, não C-com-classes-mais-um-pouco-extra-matemática-coisa.Quanto ao motivo pelo qual ele nunca foi adicionado aos padrões antes
C++11
, você deve se lembrar que os órgãos de definição de padrões têm diretrizes específicas a seguir. Por exemplo, ANSIC
foi especificamente encarregado de codificar a prática existente, não de criar uma nova linguagem. Caso contrário, eles poderiam ter enlouquecido e nos dado Ada :-)As iterações posteriores desse padrão também têm diretrizes específicas e podem ser encontradas nos documentos de justificativa (justificativa de por que o comitê tomou certas decisões, não a justificativa para a linguagem em si).
Por exemplo, o
C99
documento de justificativa especificamente leva adiante dois dosC89
princípios orientadores que limitam o que pode ser adicionado:Diretrizes (não necessariamente aquelas específicas ) são estabelecidas para os grupos de trabalho individuais e, portanto, limitam os
C++
comitês (e todos os outros grupos ISO) também.Além disso, os órgãos de definição de padrões percebem que há um custo de oportunidade (um termo econômico que significa o que você tem que abrir mão para uma decisão tomada) para cada decisão que tomam. Por exemplo, o custo de oportunidade de comprar aquela máquina de uber-gaming de $ 10.000 são relações cordiais (ou provavelmente todas as relações) com sua outra metade por cerca de seis meses.
Eric Gunnerson explica isso bem com sua explicação de -100 pontos sobre por que as coisas nem sempre são adicionadas aos produtos Microsoft - basicamente, um recurso começa 100 pontos no buraco, então tem que adicionar um pouco de valor para ser considerado.
Em outras palavras, você prefere ter um operador de energia integral (que, honestamente, qualquer codificador decente poderia fazer em dez minutos) ou multi-threading adicionado ao padrão? Para mim, prefiro ter o último e não ter que me preocupar com as diferentes implementações no UNIX e no Windows.
Eu gostaria de ver também milhares e milhares de coleção da biblioteca padrão (hashes, btrees, árvores vermelhas e pretas, dicionário, mapas arbitrários e assim por diante), mas, como afirma a lógica:
E o número de implementadores nos organismos de padrões supera em muito o número de programadores (ou pelo menos aqueles programadores que não entendem o custo de oportunidade). Se tudo isso fosse adicionado, o próximo padrão
C++
seriaC++215x
e provavelmente seria totalmente implementado por desenvolvedores de compiladores trezentos anos depois disso.De qualquer forma, essa é minha opinião (bastante volumosa) sobre o assunto. Se os votos fossem dados com base na quantidade e não na qualidade, eu logo tiraria todos os outros da água. Obrigado por ouvir :-)
fonte
to_string
e lambdas são conveniências para coisas que você já pode fazer. Suponho que se poderia interpretar "apenas uma maneira de fazer uma operação" de forma muito vaga para permitir ambos e, ao mesmo tempo, permitir quase qualquer duplicação de funcionalidade que se possa imaginar, dizendo "aha! Não! Porque a conveniência torna é uma operação sutilmente diferente da alternativa precisamente equivalente, porém mais prolixa! ". O que certamente é verdade para lambdas.pow
realmente não requer muita habilidade. Certamente eu prefiro ter o padrão fornecer algo que iria exigir muita habilidade, e resultar em minutos muito mais desperdiçados se o esforço teve que ser duplicado.Para qualquer tipo integral de largura fixa, quase todos os pares de entrada possíveis excedem o tipo, de qualquer maneira. Qual é a utilidade de padronizar uma função que não fornece um resultado útil para a grande maioria de suas entradas possíveis?
Você praticamente precisa ter um tipo de inteiro grande para tornar a função útil, e a maioria das bibliotecas de inteiro grande fornece a função.
Edit: Em um comentário sobre a questão, static_rtti escreve "A maioria das entradas causa o estouro? O mesmo é verdade para exp e double pow, não vejo ninguém reclamando." Isso está incorreto.
Vamos deixar de lado
exp
, porque isso não vem ao caso (embora na verdade tornasse meu caso mais forte) e focardouble pow(double x, double y)
. Para qual porção dos pares (x, y) esta função faz algo útil (ou seja, não simplesmente estouro ou estouro negativo)?Na verdade, vou me concentrar apenas em uma pequena parte dos pares de entrada para os quais
pow
faz sentido, porque isso será suficiente para provar meu ponto: se x for positivo e | y | <= 1, entãopow
não transborda ou underflow. Isso compreende quase um quarto de todos os pares de ponto flutuante (exatamente metade dos números de ponto flutuante não NaN são positivos, e pouco menos da metade dos números de ponto flutuante não NaN têm magnitude menor que 1). Obviamente, existem muitos outros pares de entrada para os quaispow
produz resultados úteis, mas verificamos que é pelo menos um quarto de todas as entradas.Agora vamos olhar para uma função de potência inteira de largura fixa (ou seja, não bignum). Para quais entradas de porção ele simplesmente não transborda? Para maximizar o número de pares de entrada significativos, a base deve ser assinada e o expoente não assinado. Suponha que a base e o expoente tenham
n
largura de bits. Podemos facilmente obter um limite na parte das entradas que são significativas:Assim, dos 2 ^ (2n) pares de entrada, menos de 2 ^ (n + 1) + 2 ^ (3n / 2) produzem resultados significativos. Se olharmos para o que é provavelmente o uso mais comum, inteiros de 32 bits, isso significa que algo na ordem de 1/1000 de um por cento dos pares de entrada não estouram simplesmente.
fonte
pow(x,y)
não underflow para zero para qualquer x se | y | <= 1. Há uma banda muito estreita de entradas (grande x, y muito próximo de -1) para a qual ocorre underflow, mas o resultado ainda é significativo nesse intervalo.pow
é simplesmente uma pequena tabela de pesquisa. :-)Porque não há como representar todas as potências inteiras em um int de qualquer maneira:
fonte
int pow(int base, unsigned int exponent)
efloat pow(int base, int exponent)
int pow(int base, unsigned char exponent)
é um tanto inútil de qualquer maneira. Ou a base é 0 ou 1, e o expoente não importa, é -1, caso em que apenas o último bit do expoente importa oubase >1 || base< -1
, nesse caso,exponent<256
sob pena de estouro.Essa é realmente uma pergunta interessante. Um argumento que não encontrei na discussão é a simples falta de valores de retorno óbvios para os argumentos. Vamos contar as maneiras pelas quais a
int pow_int(int, int)
função hipotética pode falhar.pow_int(0,0)
pow_int(2,-1)
A função tem pelo menos 2 modos de falha. Os inteiros não podem representar esses valores, o comportamento da função nesses casos precisaria ser definido pelo padrão - e os programadores precisariam estar cientes de como exatamente a função lida com esses casos.
No geral, deixar a função de fora parece a única opção sensata. O programador pode usar a versão de ponto flutuante com todos os relatórios de erros disponíveis.
fonte
pow
flutuador entre os flutuadores? Pegue dois grandes flutuadores, eleve um à potência do outro e você terá um Overflow. Epow(0.0, 0.0)
causaria o mesmo problema do seu segundo ponto. Seu terceiro ponto é a única diferença real entre implementar uma função de potência para inteiros e flutuantes.Resposta curta:
Uma especialização de
pow(x, n)
onden
é um número natural costuma ser útil para o desempenho de tempo . Mas o genérico da biblioteca padrãopow()
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-sen
para 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 emO(log(n))
tempo, mas quandon
é pequena, uma versão linear mais simples pode ser mais rápida. Aqui estão implementações de ambos:(Saí
x
e o valor de retorno dobra porque o resultado depow(double x, unsigned n)
caberá em um dobro com a frequência necessáriapow(double, double)
.)(Sim,
pown
é recursivo, mas quebrar a pilha é absolutamente impossível, pois o tamanho máximo da pilha será aproximadamente iguallog_2(n)
en
é um inteiro. Sen
for 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 anosx
igual ao número da iteração en
igual a 10. Nesse cenário,pown_l
venceu. glibcpow()
levou 12,0 segundos do usuário,pown
7,4 segundos do usuário epown_l
apenas 6,5 segundos do usuário. Portanto, isso não é muito surpreendente. Estávamos mais ou menos esperando isso.Então, eu deixo
x
ser constante (eu configurei para 2,5), e fiz um loopn
de 0 a 19 cem milhões de vezes. Desta vez, de forma bastante inesperada, glibcpow
venceu, e por um deslizamento de terra! Demorou apenas 2,0 segundos do usuário. Meupown
demorou 9,6 segundos epown_l
12,2 segundos. O que aconteceu aqui? Fiz outro teste para descobrir.Eu fiz a mesma coisa acima apenas com
x
igual a um milhão. Desta vez,pown
venceu a 9.6s.pown_l
levou 12,2s e glibc pow levou 16,3s. Agora está claro! glibc tempow
melhor desempenho que os três quandox
está baixo, mas pior quandox
está alto. Quandox
está alto, tempown_l
melhor desempenho quandon
está baixo e tempown
melhor desempenho quandox
está 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:Contanto que
x_expected
en_expected
sejam 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 apown_auto
chamada 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.fonte
n
. godbolt.org/z/L9Kb98 . gcc e clang falham em otimizar sua definição recursiva em um loop simples e, na verdade, ramificam em cada bit den
. (Poispown_iter(double,unsigned)
eles ainda se ramificam, mas uma implementação SSE2 ou SSE4.1 sem ramificações deve ser possível no conjunto x86 ou com intrínsecos C. Mas mesmo isso é melhor do que recursão)Uma razão para C ++ não ter sobrecargas adicionais é ser compatível com C.
C ++ 98 tem funções semelhantes a
double pow(double, int)
, mas foram removidas do C ++ 11 com o argumento de que o C99 não as incluía.http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550
Obter um resultado um pouco mais preciso também significa obter um resultado ligeiramente diferente .
fonte
O mundo está em constante evolução, assim como as linguagens de programação. A quarta parte do C decimal TR ¹ adiciona mais algumas funções a
<math.h>
. Duas famílias dessas funções podem ser de interesse para esta questão:pown
funções, que recebem um número de ponto flutuante e umintmax_t
expoente.powr
funções, que pegam dois números de pontos flutuantes (x
ey
) e calculamx
a potênciay
com a fórmulaexp(y*log(x))
.Parece que os caras padrão eventualmente consideraram esses recursos úteis o suficiente para serem integrados na biblioteca padrão. No entanto, o racional é que essas funções são recomendadas pelo padrão ISO / IEC / IEEE 60559: 2011 para números de ponto flutuante binários e decimais. Não posso dizer com certeza qual "padrão" era seguido na época do C89, mas as evoluções futuras do
<math.h>
serão provavelmente fortemente influenciadas pelas evoluções futuras do padrão ISO / IEC / IEEE 60559 .Observe que a quarta parte do TR decimal não será incluída no C2x (a próxima revisão C principal) e provavelmente será incluída posteriormente como um recurso opcional. Não há nenhuma intenção que eu conheça de incluir esta parte do TR em uma revisão futura do C ++.
¹ Você pode encontrar alguma documentação de trabalho em andamento aqui .
fonte
pown
com um expoente maior do queLONG_MAX
deveria produzir um valor diferente de usarLONG_MAX
, ou onde um valor menor queLONG_MIN
deveria produzir um valor diferente deLONG_MIN
? Eu me pergunto qual é o benefício obtido com o usointmax_t
de um expoente?Talvez porque a ALU do processador não implementou tal função para inteiros, mas existe tal instrução FPU (como Stephen aponta, é na verdade um par). Portanto, era realmente mais rápido lançar para dobrar, chamar pow com duplos, testar o estouro e lançar de volta do que implementá-lo usando aritmética de inteiros.
(por um lado, os logaritmos reduzem as potências à multiplicação, mas os logaritmos de inteiros perdem muita precisão para a maioria das entradas)
Stephen está certo que nos processadores modernos isso não é mais verdade, mas o padrão C quando as funções matemáticas foram selecionadas (C ++ apenas usava as funções C) tem agora o que, 20 anos?
fonte
pow
. O x86 tem umay log2 x
instrução (fyl2x
) que pode ser usada como a primeira parte de umapow
função, mas umapow
função escrita dessa forma leva centenas de ciclos para ser executada no hardware atual; uma rotina de exponenciação de inteiros bem escrita é várias vezes mais rápida.Aqui está uma implementação O (log (n)) realmente simples de pow () que funciona para qualquer tipo numérico, incluindo inteiros :
É melhor do que a implementação O (log (n)) do enigmaticPhysicist porque não usa recursão.
Também é quase sempre mais rápido do que sua implementação linear (desde que p> ~ 3) porque:
fonte
Na verdade, sim.
Desde C ++ 11, há uma implementação
pow(int, int)
modelada de --- e casos ainda mais gerais, consulte (7) em http://en.cppreference.com/w/cpp/numeric/math/powEDITAR: os puristas podem argumentar que isso não é correto, pois na verdade é usada uma digitação "promovida". De uma forma ou de outra, obtém-se um
int
resultado correto , ou um erro, nosint
parâmetros.fonte
pow ( Arithmetic1 base, Arithmetic2 exp )
que será lançadadouble
oulong double
se você leu a descrição: "7) Um conjunto de sobrecargas ou um modelo de função para todas as combinações de argumentos de tipo aritmético não cobertos por 1-3). Se houver algum argumento tem tipo integral, ele é convertido em double. Se qualquer argumento for long double, o tipo de retorno Promovido também será long double, caso contrário, o tipo de retorno será sempre double. "pow(1.5f, 3)
=1072693280
butpow(1.5f, float(3))
=3.375
int pow(int, int)
, mas C ++ 11 apenas fornecedouble pow(int, int)
. Veja a explicação de @phuclv.Um motivo muito simples:
Tudo na biblioteca STL é baseado nas coisas mais precisas e robustas que se possa imaginar. Claro, o int voltaria a zero (de 1/25), mas essa seria uma resposta imprecisa.
Eu concordo, é estranho em alguns casos.
fonte