Estou procurando a maneira mais rápida de determinar se um long
valor é um quadrado perfeito (ou seja, sua raiz quadrada é outro número inteiro):
- Fiz isso da maneira mais fácil, usando a
Math.sqrt()
função interna, mas estou me perguntando se existe uma maneira de fazê-lo mais rapidamente, restringindo-se ao domínio somente número inteiro. - Manter uma tabela de pesquisa é impraticável (já que existem cerca de 2 31,5 inteiros cujo quadrado é menor que 2 63 ).
Aqui está a maneira muito simples e direta de fazer agora:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Nota: Estou usando esta função em muitos problemas do Project Euler . Portanto, ninguém mais terá que manter esse código. E esse tipo de micro-otimização pode realmente fazer a diferença, já que parte do desafio é executar todos os algoritmos em menos de um minuto, e essa função precisará ser chamada milhões de vezes em alguns problemas.
Eu tentei as diferentes soluções para o problema:
- Após testes exaustivos, descobri que
0.5
não é necessário adicionar ao resultado Math.sqrt (), pelo menos não na minha máquina. - A raiz quadrada inversa rápida foi mais rápida, mas apresentou resultados incorretos para n> = 410881. No entanto, como sugerido por BobbyShaftoe , podemos usar o hack FISR para n <410881.
- O método de Newton era um pouco mais lento que
Math.sqrt()
. Provavelmente porqueMath.sqrt()
usa algo semelhante ao Método de Newton, mas implementado no hardware, por isso é muito mais rápido do que em Java. Além disso, o método de Newton ainda exigia o uso de duplos. - Um método de Newton modificado, que usava alguns truques para envolver apenas matemática inteira, exigia alguns hacks para evitar o estouro (eu quero que essa função funcione com todos os inteiros positivos assinados em 64 bits) e ainda era mais lenta que isso
Math.sqrt()
. - Costeleta binária foi ainda mais lenta. Isso faz sentido, porque o chop binário exigirá, em média, 16 passagens para encontrar a raiz quadrada de um número de 64 bits.
- De acordo com os testes de John, o uso de
or
instruções é mais rápido em C ++ do que o de aswitch
, mas em Java e C # parece não haver diferença entreor
eswitch
. - Eu também tentei fazer uma tabela de pesquisa (como uma matriz estática privada de 64 valores booleanos). Então, em vez de um switch ou uma
or
declaração, eu diria apenasif(lookup[(int)(n&0x3F)]) { test } else return false;
. Para minha surpresa, isso foi (apenas um pouco) mais lento. Isso ocorre porque os limites da matriz são verificados em Java .
((1<<(n&15))|65004) != 0
, em vez de ter três verificações separadas.Respostas:
Eu descobri um método que funciona ~ 35% mais rápido que o seu código 6bits + Carmack + sqrt, pelo menos com minha CPU (x86) e linguagem de programação (C / C ++). Seus resultados podem variar, principalmente porque eu não sei como o fator Java se desenvolverá.
Minha abordagem é tríplice:
int64 x
.)z = r - x * x
e defino t como a maior potência de 2, dividindo z com um pequeno truque. Isso me permite pular valores t que não afetariam o valor de r de qualquer maneira. O valor inicial pré-calculado no meu caso escolhe o módulo 8192 de raiz quadrada "menor positivo".Mesmo que esse código não funcione mais rápido, espero que você aproveite algumas das idéias que ele contém. Segue código completo e testado, incluindo as tabelas pré-computadas.
fonte
9 < 0 => false
,9&2 => 0
,9&7 == 5 => false
,9&11 == 8 => false
.Estou muito atrasado para a festa, mas espero dar uma resposta melhor; mais curto e (supondo que minha referência esteja correta) também muito mais rápido .
O primeiro teste captura a maioria dos não quadrados rapidamente. Ele usa uma tabela de 64 itens compactada em um longo período, para que não haja custo de acesso ao array (verificação indireta e de limites). Para uma uniformidade aleatória
long
, há uma probabilidade de 81,25% de terminar aqui.O segundo teste captura todos os números com um número ímpar de dois em sua fatoração. O método
Long.numberOfTrailingZeros
é muito rápido, pois é inserido no JIT em uma única instrução i86.Depois de eliminar os zeros à direita, o terceiro teste lida com números que terminam com 011, 101 ou 111 em binário, que não são quadrados perfeitos. Ele também se importa com números negativos e também lida com 0.
O teste final volta à
double
aritmética. Comodouble
possui apenas mantissa de 53 bits, a conversão delong
paradouble
inclui arredondamento para grandes valores. No entanto, o teste está correto (a menos que a prova esteja errada).Tentar incorporar a ideia mod255 não teve sucesso.
fonte
goodMask
teste faz isso, mas antes do turno certo. Então você teria que repetir, mas dessa maneira é mais simples e o AFAIK é um pouco mais rápido e igualmente bom.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Você terá que fazer alguns testes comparativos. O melhor algoritmo dependerá da distribuição de suas entradas.
Seu algoritmo pode ser quase ideal, mas você pode fazer uma verificação rápida para descartar algumas possibilidades antes de chamar sua rotina de raiz quadrada. Por exemplo, observe o último dígito do seu número em hexadecimal, digitando "e". Os quadrados perfeitos só podem terminar em 0, 1, 4 ou 9 na base 16. Portanto, para 75% de suas entradas (supondo que elas estejam distribuídas uniformemente), você pode evitar uma chamada para a raiz quadrada em troca de alguns ajustes muito rápidos.
Kip comparou o código a seguir implementando o truque hexadecimal. Ao testar os números de 1 a 100.000.000, esse código foi executado duas vezes mais rápido que o original.
Quando testei o código análogo no C ++, ele realmente ficou mais lento que o original. No entanto, quando eliminei a instrução switch, o truque hexadecimal mais uma vez torna o código duas vezes mais rápido.
Eliminar a instrução switch teve pouco efeito no código C #.
fonte
Eu estava pensando nos momentos horríveis que passei no curso de Análise Numérica.
E então me lembro, havia essa função circulando a rede do código-fonte do Quake:
Que basicamente calcula uma raiz quadrada, usando a função de aproximação de Newton (não consigo lembrar o nome exato).
Deve ser utilizável e até mais rápido, é de um dos jogos fenomenais do software de identificação!
Está escrito em C ++, mas não deve ser muito difícil reutilizar a mesma técnica em Java depois de ter a idéia:
Eu o encontrei originalmente em: http://www.codemaestro.com/reviews/9
O método de Newton explicado na wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method
Você pode seguir o link para obter mais explicações sobre como ele funciona, mas se você não se importa muito, é isso que eu lembro da leitura do blog e do curso de Análise Numérica:
* (long*) &y
é basicamente uma função rapidamente convertido-para-longa para que as operações de inteiros podem ser aplicados sobre os bytes brutos.0x5f3759df - (i >> 1);
linha é um valor inicial pré-calculado para a função de aproximação.* (float*) &i
converte o valor novamente em ponto flutuante.y = y * ( threehalfs - ( x2 * y * y ) )
linha basicamente itera o valor sobre a função novamente.A função de aproximação fornece valores mais precisos quanto mais você iterar a função sobre o resultado. No caso de Quake, uma iteração é "boa o suficiente", mas se não fosse por você ... você poderia adicionar a iteração necessária.
Isso deve ser mais rápido, pois reduz o número de operações de divisão realizadas no quadrado ingênuo de raiz para uma simples divisão por 2 (na verdade, uma
* 0.5F
operação de multiplicação) e substitui-a por um número fixo de operações de multiplicação.fonte
Não tenho certeza se seria mais rápido, ou mesmo preciso, mas você poderia usar o algoritmo Magical Square Root , de John Carmack , para resolver a raiz quadrada mais rapidamente. Provavelmente, você poderia facilmente testar isso para todos os números inteiros de 32 bits possíveis e validar se realmente obteve os resultados corretos, pois isso é apenas uma aproximação. No entanto, agora que penso nisso, o uso de duplas também se aproxima, então não tenho certeza de como isso entraria em jogo.
fonte
Se você fizer um corte binário para tentar encontrar a raiz quadrada "certa", poderá detectar com facilidade se o valor obtido é próximo o suficiente para informar:
Então, tendo calculado
n^2
, as opções são:n^2 = target
: pronto, retorne verdadeiron^2 + 2n + 1 > target > n^2
: você está perto, mas não é perfeito: return falsen^2 - 2n + 1 < target < n^2
: idemtarget < n^2 - 2n + 1
: costeleta binária em uma parte inferiorn
target > n^2 + 2n + 1
: costeleta binária em uma maiorn
(Desculpe, isso usa
n
como seu palpite atual etarget
como parâmetro. Peça desculpas pela confusão!)Não sei se isso será mais rápido ou não, mas vale a pena tentar.
EDIT: O chop binário não precisa incluir todo o intervalo de números inteiros;
(2^x)^2 = 2^(2x)
portanto, depois de encontrar o bit mais definido no seu destino (o que pode ser feito com um truque de manipulação de bits; esqueço exatamente como) você pode obter rapidamente uma variedade de respostas em potencial. Lembre-se, uma ingestão binária de binário ainda vai levar apenas 31 ou 32 iterações.fonte
Fiz minha própria análise de vários algoritmos neste segmento e obtive alguns novos resultados. Você pode ver esses resultados antigos no histórico de edições desta resposta, mas eles não são precisos, como cometi um erro, e perdi tempo analisando vários algoritmos que não estão próximos. No entanto, tirando lições de várias respostas diferentes, agora tenho dois algoritmos que esmagam o "vencedor" desse segmento. Aqui está a coisa principal que faço de maneira diferente de todos os outros:
No entanto, essa linha simples, que na maioria das vezes adiciona uma ou duas instruções muito rápidas, simplifica muito a
switch-case
instrução em uma instrução if. No entanto, isso pode aumentar o tempo de execução se muitos dos números testados tiverem um poder significativo de dois fatores.Os algoritmos abaixo são os seguintes:
Aqui está um exemplo de tempo de execução se os números forem gerados usando
Math.abs(java.util.Random.nextLong())
E aqui está um exemplo de tempo de execução, se for executado apenas no primeiro milhão de longos:
Como você pode ver,
DurronTwo
é melhor para entradas grandes, porque ele usa o truque de mágica com muita frequência, mas é derrotado em comparação com o primeiro algoritmo eMath.sqrt
porque os números são muito menores. Enquanto isso, o mais simplesDurron
é um grande vencedor, porque nunca precisa se dividir por 4 muitas e muitas vezes no primeiro milhão de números.Aqui está
Durron
:E
DurronTwo
E meu chicote de referência: (Requer o Google caliper 0.1-rc5)
ATUALIZAÇÃO: Criei um novo algoritmo que é mais rápido em alguns cenários, mais lento em outros, obtive benchmarks diferentes com base em entradas diferentes. Se calcularmos o módulo
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, podemos eliminar 97,82% dos números que não podem ser quadrados. Isso pode ser feito (mais ou menos) em uma linha, com 5 operações bit a bit:O índice resultante é 1) o resíduo, 2) o resíduo
+ 0xFFFFFF
ou 3) o resíduo+ 0x1FFFFFE
. Obviamente, precisamos ter uma tabela de pesquisa para o módulo de resíduos0xFFFFFF
, que é sobre um arquivo de 3mb (nesse caso, armazenado como números decimais de texto ascii, não ideal, mas claramente improvável com umByteBuffer
e assim por diante. t importa tanto. Você pode encontrar o arquivo aqui (ou gerar it yourself):Eu carrego-o em uma
boolean
matriz como esta:Exemplo de tempo de execução. Ele bateu
Durron
(versão um) em todos os testes que eu executei.fonte
sqrtps
taxa de transferência SIMD ou mesmosqrtpd
(precisão dupla) não são muito ruins no Skylake, mas não são muito melhores do que a latência em CPUs antigas. De qualquer forma, 7-cpu.com/cpu/Haswell.html possui alguns bons números experimentais e páginas para outras CPUs. De Agner Nevoeiro guia microarch pdf tem alguns números de latência de cache para Intel e AMD uarches: agner.org/optimizedouble
precisão para evitar arredondar algum número inteiro fora do intervalo + -2 ^ 24 (para que um número inteiro de 32 bits possa ficar fora dele) esqrtpd
é mais lento que osqrtps
processamento e apenas a metade do número de elementos por instrução (por vetor SIMD) .Deve ser muito mais rápido usar o método de Newton para calcular a Raiz quadrada inteira , depois quadrar esse número e verificar como você faz na sua solução atual. O método de Newton é a base da solução Carmack mencionada em algumas outras respostas. Você deve conseguir uma resposta mais rápida, pois está interessado apenas na parte inteira da raiz, permitindo interromper o algoritmo de aproximação mais cedo.
Outra otimização que você pode tentar: Se a Raiz Digital de um número não terminar em 1, 4, 7 ou 9, o número não será um quadrado perfeito. Isso pode ser usado como uma maneira rápida de eliminar 60% de suas entradas antes de aplicar o algoritmo de raiz quadrada mais lento.
fonte
Math.sqrt()
funciona com dobras como parâmetros de entrada, para que você não obtenha resultados precisos para números inteiros maiores que 2 ^ 53 .fonte
Apenas para constar, outra abordagem é usar a decomposição principal. Se todos os fatores da decomposição forem pares, o número será um quadrado perfeito. Então, o que você quer é ver se um número pode ser decomposto como um produto de quadrados de números primos. Obviamente, você não precisa obter essa decomposição, apenas para ver se ela existe.
Primeiro construa uma tabela de quadrados de números primos menores que 2 ^ 32. Isso é muito menor que uma tabela de todos os números inteiros até esse limite.
Uma solução seria assim:
Eu acho que é um pouco enigmático. O que ele faz é verificar a cada passo que o quadrado de um número primo divide o número de entrada. Se isso acontecer, ele divide o número pelo quadrado o máximo possível, para remover esse quadrado da decomposição principal. Se, por esse processo, chegamos a 1, o número de entrada foi uma decomposição do quadrado dos números primos. Se o quadrado se tornar maior que o número em si, não há como esse quadrado, ou qualquer quadrado maior, poder dividi-lo; portanto, o número não pode ser uma decomposição de quadrados de números primos.
Dado o sqrt de hoje em dia feito em hardware e a necessidade de calcular números primos aqui, acho que essa solução é muito mais lenta. Mas deve dar melhores resultados do que a solução com sqrt, que não funcionará acima de 2 ^ 54, como diz mrzl em sua resposta.
fonte
sqrtsd
rendimento do Core2 é de um por 6-58c. Éidiv
um por 12-36 motos. (latências semelhantes às taxas de transferência: nenhuma unidade é canalizada).Tem sido apontado que os últimos
d
dígitos de um quadrado perfeito só podem assumir certos valores. Os últimosd
dígitos (na baseb
) de um númeron
são os mesmos que o restante quandon
é dividido porb
d
, ie. em notação Cn % pow(b, d)
.Isso pode ser generalizado para qualquer módulo
m
, ie.n % m
pode ser usado para excluir uma porcentagem de números de quadrados perfeitos. O módulo que você está usando atualmente é 64, o que permite 12, ou seja. 19% dos restantes, como quadrados possíveis. Com um pouco de codificação, encontrei o módulo 110880, que permite apenas 2016, ou seja. 1,8% dos restantes como quadrados possíveis. Portanto, dependendo do custo de uma operação de módulo (por exemplo, divisão) e de uma pesquisa de tabela versus uma raiz quadrada em sua máquina, o uso desse módulo pode ser mais rápido.A propósito, se o Java tem uma maneira de armazenar uma matriz compactada de bits para a tabela de pesquisa, não a utilize. 110880 Palavras de 32 bits não são muita RAM hoje em dia e buscar uma palavra de máquina será mais rápida do que buscar um único bit.
fonte
idiv
) é igual ou pior em custo ao FP sqrt (sqrtsd
) no hardware x86 atual. Além disso, discordo completamente de evitar campos de bits. A taxa de acertos do cache será muito melhor com um campo de bits, e testar um pouco em um campo de bits é apenas uma ou duas instruções mais simples do que testar um byte inteiro. (Para pequenas mesas que se encaixam no cache, mesmo quando não bitfields, um array de bytes seria melhor, e não de 32 bits ints x86 tem acesso de byte único com igual velocidade para dword de 32 bits..)Um problema inteiro merece uma solução inteira. portanto
Faça uma pesquisa binária nos números inteiros (não negativos) para encontrar o maior número inteiro t tal que
t**2 <= n
. Em seguida, teste ser**2 = n
exatamente. Isso leva tempo O (log n).Se você não souber pesquisar binariamente os números inteiros positivos porque o conjunto é ilimitado, é fácil. Você começa calculando sua função crescente f (acima
f(t) = t**2 - n
) com potências de dois. Quando você vê que fica positivo, você encontra um limite superior. Então você pode fazer a pesquisa binária padrão.fonte
O((log n)^2)
porque a multiplicação não é constante, mas na verdade tem um limite inferiorO(log n)
, o que se torna aparente ao trabalhar com grandes números de multi-precisão. Mas o escopo deste wiki parece ser de 64 bits, então talvez seja nbd.A seguinte simplificação da solução de maaartinus parece reduzir alguns pontos percentuais do tempo de execução, mas não sou bom o suficiente em benchmarking para produzir um benchmark em que possa confiar:
Vale a pena verificar como omitir o primeiro teste,
afetaria o desempenho.
fonte
Para desempenho, muitas vezes você precisa fazer alguns compromissos. Outros expressaram vários métodos, no entanto, você notou que o hack de Carmack era mais rápido até certos valores de N. Então, você deve verificar o "n" e, se for menor que esse número N, use o hack de Carmack, caso contrário, use outro método descrito nas respostas aqui.
fonte
Esta é a implementação Java mais rápida que eu pude criar, usando uma combinação de técnicas sugeridas por outras pessoas neste segmento.
Também experimentei essas modificações, mas elas não ajudaram no desempenho:
fonte
Você deve se livrar da parte de 2 potências de N desde o início.
2ª Edição A expressão mágica para m abaixo deve ser
e não como escrito
Fim da 2ª edição
1ª Edição:
Pequena melhoria:
Fim da 1ª edição
Agora continue como de costume. Dessa forma, no momento em que você chega à parte do ponto flutuante, você já se livra de todos os números cuja parte de 2 potências é ímpar (cerca da metade) e então considera apenas 1/8 do que resta. Ou seja, você executa a parte do ponto flutuante em 6% dos números.
fonte
O Projeto Euler é mencionado nas tags e muitos dos problemas nele requerem verificação de números >>
2^64
. A maioria das otimizações mencionadas acima não funciona facilmente quando você está trabalhando com um buffer de 80 bytes.Eu usei o java BigInteger e uma versão ligeiramente modificada do método de Newton, que funciona melhor com números inteiros. O problema era que os quadrados exatos
n^2
convergiam para em(n-1)
vez den
porquen^2-1 = (n-1)(n+1)
e o erro final estava apenas um passo abaixo do divisor final e o algoritmo foi encerrado. Foi fácil corrigir isso adicionando um ao argumento original antes de calcular o erro. (Adicione dois para raízes de cubo, etc.)Um bom atributo desse algoritmo é que você pode dizer imediatamente se o número é um quadrado perfeito - o erro final (não a correção) no método de Newton será zero. Uma modificação simples também permite calcular rapidamente em
floor(sqrt(x))
vez do número inteiro mais próximo. Isso é útil com vários problemas de Euler.fonte
Isso é um retrabalho de decimal para binário do antigo algoritmo da calculadora Marchant (desculpe, não tenho uma referência), em Ruby, adaptado especificamente para esta pergunta:
Aqui está uma descrição de algo semelhante (por favor, não vote em mim por estilo de codificação / cheiros ou O / O desajeitado - é o algoritmo que conta, e C ++ não é minha língua materna). Nesse caso, estamos procurando por resíduo == 0:
fonte
A chamada sqrt não é perfeitamente precisa, como foi mencionado, mas é interessante e instrutivo que ela não exagere nas outras respostas em termos de velocidade. Afinal, a sequência de instruções em linguagem assembly para um sqrt é pequena. A Intel tem uma instrução de hardware, que não é usada pelo Java, acredito, porque não está em conformidade com o IEEE.
Então, por que é lento? Como o Java está realmente chamando uma rotina C por meio da JNI, é mais lento fazê-lo do que chamar uma sub-rotina Java, que é mais lenta do que fazê-la em linha. Isso é muito chato, e o Java deveria ter encontrado uma solução melhor, ou seja, criar chamadas de biblioteca de ponto flutuante, se necessário. Ah bem.
No C ++, suspeito que todas as alternativas complexas perderiam velocidade, mas não as verifiquei todas. O que eu fiz e o que o pessoal de Java achará útil é um hack simples, uma extensão do teste de caso especial sugerido por A. Rex. Use um único valor longo como uma matriz de bits, que não seja verificada nos limites. Dessa forma, você tem uma pesquisa booleana de 64 bits.
A rotina isPerfectSquare5 é executada em cerca de 1/3 do tempo na minha máquina core2 duo. Eu suspeito que novos ajustes nas mesmas linhas poderiam reduzir o tempo ainda mais, em média, mas toda vez que você verifica, você está trocando mais testes por mais eliminações, para que você não possa ir muito mais longe nessa estrada.
Certamente, em vez de ter um teste separado para negativo, você pode verificar os 6 bits mais altos da mesma maneira.
Observe que tudo o que estou fazendo é eliminar possíveis quadrados, mas quando tenho um caso em potencial, preciso chamar o original, inPerfectSquare.
A rotina init2 é chamada uma vez para inicializar os valores estáticos de pp1 e pp2. Observe que, na minha implementação em C ++, estou usando a assinatura não assinada por muito tempo, portanto, como você foi assinado, você teria que usar o operador >>>.
Não há necessidade intrínseca de limitar a verificação da matriz, mas o otimizador de Java precisa descobrir essas coisas rapidamente, para que eu não as culpe por isso.
fonte
pp2
? Entendo quepp1
é usado para testar os seis bits menos significativos, mas não acredito que testar os próximos seis bits faça algum sentido.Eu gosto da ideia de usar um método quase correto em algumas das entradas. Aqui está uma versão com um "deslocamento" mais alto. O código parece funcionar e passa no meu caso de teste simples.
Basta substituir o seu:
código com este:
fonte
Considerando o tamanho geral dos bits (embora eu tenha usado um tipo específico aqui), tentei projetar algo simplista como abaixo. A verificação simples e óbvia de 0,1,2 ou <0 é necessária inicialmente. A seguir, é simples, no sentido de que ele não tenta usar nenhuma função matemática existente. A maior parte do operador pode ser substituída por operadores bit a bit. Ainda não testei com nenhum dado de benchmark. Eu não sou especialista em matemática ou design de algoritmos de computador em particular, eu adoraria vê-lo apontando um problema. Eu sei que há muitas chances de melhoria lá.
fonte
Eu verifiquei todos os resultados possíveis quando os últimos n bits de um quadrado são observados. Ao examinar sucessivamente mais bits, até 5/6 das entradas podem ser eliminadas. Na verdade, eu projetei isso para implementar o algoritmo de fatoração de Fermat, e é muito rápido lá.
O último bit de pseudocódigo pode ser usado para estender os testes e eliminar mais valores. Os testes acima são para k = 0, 1, 2, 3
Primeiro ele testa se tem um resíduo quadrado com módulos de potência de dois, depois testa com base em um módulo final e depois usa o Math.sqrt para fazer um teste final. Eu tive a ideia do post principal e tentei estendê-la. Agradeço quaisquer comentários ou sugestões.
Atualização: Usando o teste por um módulo (modSq) e uma base de módulo 44352, meu teste é executado em 96% do tempo daquele na atualização do OP para números de até 1.000.000.000.
fonte
Aqui está uma solução de dividir e conquistar.
Se a raiz quadrada de um número natural (
number
) for um número natural (solution
), você poderá determinar facilmente um intervalo comsolution
base no número de dígitos denumber
:number
tem 1 dígito:solution
no intervalo = 1 - 4number
tem 2 dígitos:solution
no intervalo = 3 - 10number
tem 3 dígitos:solution
no intervalo = 10 - 40number
tem 4 dígitos:solution
no intervalo = 30 - 100number
tem 5 dígitos:solution
no intervalo = 100 - 400Observe a repetição?
Você pode usar esse intervalo em uma abordagem de pesquisa binária para verificar se existe um
solution
para o qual:Aqui está o código
Aqui está a minha classe SquareRootChecker
E aqui está um exemplo de como usá-lo.
fonte
toString
é uma operação incrivelmente cara em comparação com os operadores bit a bit. Portanto, para satisfazer o objetivo da pergunta - desempenho - você deve usar operadores bit a bit em vez de 10 strings de base. Mais uma vez, eu realmente gosto do seu conceito. Não obstante, sua implementação (como está agora) é de longe a mais lenta dentre todas as soluções possíveis postadas para a pergunta.Se a velocidade é uma preocupação, por que não particionar o conjunto mais comum de entradas e seus valores em uma tabela de pesquisa e, em seguida, fazer o algoritmo mágico otimizado que você criar para casos excepcionais?
fonte
Deveria ser possível empacotar o 'não pode ser um quadrado perfeito se os últimos dígitos do X forem N' com muito mais eficiência do que isso! Usarei ints de 32 bits em java e produzirei dados suficientes para verificar os últimos 16 bits do número - são 2048 valores int hexadecimais.
...
Está bem. Ou eu me deparei com uma teoria dos números que está um pouco além de mim ou há um erro no meu código. De qualquer forma, aqui está o código:
e aqui estão os resultados:
(ed: elided por fraco desempenho em prettify.js; veja o histórico de revisões para ver.)
fonte
Método de Newton com aritmética inteira
Se você deseja evitar operações não inteiras, use o método abaixo. Ele basicamente usa o Método de Newton modificado para aritmética inteira.
Esta implementação não pode competir com soluções que usam
Math.sqrt
. No entanto, seu desempenho pode ser aprimorado usando os mecanismos de filtragem descritos em algumas das outras postagens.fonte
Calcular raízes quadradas pelo método de Newton é terrivelmente rápido ... desde que o valor inicial seja razoável. No entanto, não existe um valor inicial razoável e, na prática, terminamos com o comportamento de bissecção e log (2 ^ 64).
Para ser realmente rápido, precisamos de uma maneira rápida de obter um valor inicial razoável, e isso significa que precisamos descer para a linguagem de máquina. Se um processador fornece uma instrução como POPCNT no Pentium, que conta os zeros iniciais, podemos usar isso para ter um valor inicial com metade dos bits significativos. Com cuidado, podemos encontrar um número fixo de etapas de Newton que sempre serão suficientes. (Por isso, precedendo a necessidade de fazer um loop e ter uma execução muito rápida.)
Uma segunda solução está passando pelo recurso de ponto flutuante, que pode ter um cálculo rápido de sqrt (como o coprocessador i87). Mesmo uma excursão via exp () e log () pode ser mais rápida do que Newton degenerou em uma pesquisa binária. Há um aspecto complicado nisso: uma análise dependente do processador do que e se é necessário o refinamento posterior.
Uma terceira solução resolve um problema ligeiramente diferente, mas vale a pena mencionar porque a situação é descrita na pergunta. Se você deseja calcular muitas raízes quadradas para números que diferem um pouco, você pode usar a iteração de Newton, se nunca reinicializar o valor inicial, mas apenas deixá-lo onde o cálculo anterior parou. Eu usei isso com sucesso em pelo menos um problema de Euler.
fonte
Raiz quadrada de um número, dado que o número é um quadrado perfeito.
A complexidade é log (n)
fonte
Se você deseja velocidade, considerando que seus números inteiros são de tamanho finito, suspeito que a maneira mais rápida envolveria (a) particionar os parâmetros por tamanho (por exemplo, em categorias pelo maior conjunto de bits) e depois verificar o valor em uma matriz de quadrados perfeitos dentro desse intervalo.
fonte
Com relação ao método Carmac, parece que seria bastante fácil iterar mais uma vez, o que deve dobrar o número de dígitos de precisão. Afinal, é um método iterativo extremamente truncado - o de Newton, com um bom primeiro palpite.
Em relação ao seu melhor atual, vejo duas micro-otimizações:
Ou seja:
Melhor ainda pode ser um simples
Obviamente, seria interessante saber quantos números são selecionados em cada ponto de verificação - duvido que os cheques sejam verdadeiramente independentes, o que torna as coisas complicadas.
fonte