Algoritmo de precisão de raiz quadrada inteira de precisão arbitrária?


9

Existem algoritmos subquadráticos conhecidos para calcular o piso da raiz quadrada de um nnúmero inteiro de bit?

O algoritmo ingênuo seria algo como

def sqrt(x):
    r = 0
    i = x.bit_length() // 2
    while i >= 0:
        inc = (r << (i+1)) + (1 << (i*2))
        if inc <= x:
            x -= inc
            r += 1 << i
        i -= 1
    return r

Isso requer O(n)iterações, cada uma envolvendo adições que são de O(n)tempo, portanto é a O(n^2)hora geral. Existe algo mais rápido? Eu sei que, no caso da multiplicação, existem algoritmos especiais que se saem melhor que o tempo quadrático, mas não consigo encontrar nada para raízes quadradas.


Minha resposta para algo relacionado pode ajudar cs.stackexchange.com/a/37338/12052 . O único problema é que uma parte da equação necessária você precisaria encontrar empiricamente para ajustar sua precisão.
Francesco Gramano

@FrancescoGramano: Desculpe, acho que não ajuda.
Aryabhata 26/01

btw, este requisito sub-quadrático é parte de um problema maior? Porque a diferença entre o quadrático simples e o sub-quadrático complicado pode não ser tão grande na prática. Ou é apenas de interesse teórico?
Aryabhata

@Aryabhata Desculpe, não vi seu comentário anteriormente. Não, não faz parte de um problema maior, apenas curiosidade.
Antimony

Respostas:


5

Você pode usar o método de Newton ou qualquer outro método para encontrar aproximações às raízes do polinômio .p(x)=x2c

A taxa de convergência para o método de Newton será quadrática, o que significa que o número de bits corretos dobra em cada iteração. Isso significa que as iterações do método de Newton são suficientes.O(lgn)

Cada iteração do método de Newton calcula

xj+1=xj(xj2c)/(2xj)=0.5xj+c2xj.

A complexidade de bits da multiplicação é O ( b lg b ) , para multiplicar dois números inteiros de b bits (ignorando os fatores lg lg b ). A complexidade de bits para divisão (para b bits de precisão) é a mesma. Portanto, cada iteração pode ser calculada em operações O ( n lg n ) . Multiplicando por O ( lg n ) iterações, descobrimos que o tempo de execução geral para calcular a raiz quadrada em n bits de precisão é O ( n ( lg nO (blgb)blglgbbO (nlgn)O(lgn)n . Isso é sub-quadrático.O (n(lgn)2)

Penso que uma análise mais cuidadosa mostra que isso pode ser aprimorado para O ( n lg n ) tempo de execução (levando em consideração que precisamos apenas conhecer cada x j dentro de j bits de precisão, em vez de n bits de precisão) . No entanto, mesmo a análise mais básica já mostra um tempo de execução claramente subquadrático.O (nlgn)xjjn


Em um binário também tem uma excelente estimativa inicial utilizando a identidade . Em vez de calcular o log, pode-se aproximar o log 2 x como o número de dígitos em x . Por exemplo, log 2 101011 6 . x1/2=21/2log2xlog2xxlog21010116
Nick Alger

@ DW: Mas não estamos procurando uma raiz quadrada inteira? Se você faz a iteração do método de newton usando apenas aritmética inteira, precisamos de alguma justificativa adicional para a afirmação , não é? Caso contrário, já estamos assumindo uma precisão suficientemente grande ... Desculpe se estou perdendo algo óbvio. O(logn)
Aryabhata

@DW: "A taxa de convergência para o método de Newton" não será quadrática se e não sei o que acontece com valores de c que não são reais não negativos.c=0cSua estimativa para a complexidade de bits da multiplicação é mais rigorosa do que sugere sua observação a seguir .Além disso, "precisamos conhecer cada em aproximadamente" 2xj "bits de precisão".2j

@Aryabhata: Não estamos "procurando por uma raiz quadrada inteira"; estamos procurando "o chão da raiz quadrada". Você está certo sobre o problema aritmético inteiro, embora as mesmas complexidades de bits sejam válidas para operações de ponto flutuante.

11
@RickyDemer, sim, é um caso especial, porque, então, a raiz da p ( x ) tem multiplicidade 2, mas quando c > 0 , a raiz tem multiplicidade 1, de modo método de Newton não têm convergência quadrática. Suponho que ninguém usaria o método de Newton para calcular a raiz quadrada de c = 0 (porque a raiz quadrada de zero é obviamente zero). então o que você está tentando dizer? O seu comentário é um comentário trivial que é abordado adicionando algo à minha resposta que diz "caso especial a raiz quadrada de zero" ou há algo mais profundo aqui que estou perdendo? c=0p(x)c>0c=0
DW

6

Um dos problemas com o método de Newton é que ele requer uma operação de divisão em cada iteração, que é a operação inteira básica mais lenta.

O método de Newton para a raiz quadrada recíproca , no entanto, não. Se é o número para o qual você deseja encontrar 1x , itere:1x

ri+1=12ri(3xri2)

Isso geralmente é expresso como:

d i = 1 - w i x r i + 1 = r i + r i d i

wi=ri2
di=1wix
ri+1=ri+ridi2

São três operações de multiplicação. A divisão por dois pode ser implementada como shift-right.

Agora, o problema é que não é um número inteiro. No entanto, você pode manipulá-lo dessa maneira implementando o ponto flutuante manualmente e executando várias operações de turno para compensar quando apropriado.r

Primeiro, vamos redimensionar :x

x=22ex

onde gostaríamos que fosse maior que, mas próximo a 1 . Se executarmos o algoritmo acima em x ′ em vez de x , encontraremos r =x1xx. Então,r=1xx=2erx .

Agora vamos dividir r em uma mantissa e expoente:

ri=2eiri

onde é um número inteiro. Intuitivamente, um e i representam a precisão da resposta.riei

Sabemos que o método de Newton praticamente dobra o número de dígitos significativos precisos. Para que possamos escolher:

ei+1=2ei

Com um pouco de manipulação, encontramos:

w i = r i 2 x i = x

ei+1=2ei
wi=ri2
di=2ei+1-wi xi
xi=x22eei+1
di=2ei+1wixi2ei+1
ri+1=2eiriridi2ei+1

A cada iteração:

xrix2e+ei

As an example, let's try calculating the square root of x=263. We happen to know that the answer is 2312. The reciprocal square root is 12231, so we'll set e=31 (this is the scale of the problem) and for our initial guess we'll pick r0=3 and e0=2. (That is, we pick 34 for our initial estimate to 12.)

Then:

e1=4,r1=11
e2=8,r2=180
e3=16,r3=46338
e4=32,r4=3037000481

We can work out when to stop iterating by comparing ei to e; if I've calculated correctly, ei>2e should be good enough. We'll stop here, though, and find:

2633037000481×263231+32=3037000481

The correct integer square root is 3037000499, so we're pretty close. We could do another iteration, or do an optimised final iteration which doesn't double ei. The details are left as an exercise.

To analyse the complexity of this method, note that multiplying two b-bit integers takes O(blogb) operations. However, we have arranged things so that ri<2ei. So the multiplication to calculate wi multiplies two ei-bit numbers to produce a ei+1-bit number, and the other two multiplications multiply two ei+1-bit numbers to produce a 2ei+1-bit number.

In each case, the number of operations per iteration is O(eilogei), and there are O(loge) iterations required. The final multiplication is on the order of O(2elog2e) operations. So the overall complexity is O(elog2e) operations, which is sub-quadratic in the number of bits in x. That ticks all the boxes.

However, this analysis hides an important principle which everyone working with large integers should keep in mind: because multiplication is superlinear in the number of bits, any multiplication operations should only be performed on integers which have the roughly the magnitude of the current precision (and, I might add, you should try to multiply numbers together which have a similar order of magnitude). Using integers larger than that is a waste of effort. Constant factors matter, and for large integers, they matter a lot.

As a final observation, two of the multiplications are of the form ab2c. Clearly it's wasteful to compute the all the bits of ab only to throw c of them away with a right-shift. Implementing a smart multiplication method which takes this into account is also left as an exercise.


This is great stuff. One comment, though: Isn't the bit-complexity of division asymptotically approximately the same as the bit-complexity of multiplication? So you're talking about something that gives a constant factor improvement, not an asymptotic improvement, right? That wasn't entirely clear from your answer.
D.W.

You say that multiplying two b-bit integers takes O(blgb) bit operations. I think the correct answer is something like O(blgb(lglgb)O(1)) (right?). You might want to indicate that you are ignoring poly-log-log factors (e.g., by putting a tilde over your big O, or something).
D.W.

1
@D.W. : No, he says that "multiplying two b-bit integers takes O(blogb) operations." The word "bit" only appears once in that; otherwise I would've already pointed that out.

It is a matter of constant factors, yes. The best large integer division algorithms use a technique very similar to the whole algorithm, such as Newton-Raphson iteration and doubling the effective precision on each iteration. A Newton-Raphson loop within a Newton-Raphson loop piles on the constant factors! Ricky Demer is correct; I was thinking in the word RAM model. I probably should have mentioned this.
Pseudonym
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.