Racine carrée entière en python

y a-t-il une racine carrée entière quelque part dans python, ou dans les bibliothèques standard? Je veux qu'il soit exact (c.-à-d. retourner un entier), et l'écorce s'il n'y a pas de solution.

au moment où j'ai roulé mon propre naïf:

def isqrt(n):
    i = int(math.sqrt(n) + 0.5)
    if i**2 == n:
        return i
    raise ValueError('input was not a perfect square')

mais c'est laid et je n'y crois pas vraiment pour les grands entiers. J'ai pu parcourir les places et si j'ai dépassé la valeur, mais je présume qu'il est un peu lent à faire quelque chose comme ça. Aussi, Je je suppose que je serais probablement en train de réinventer la roue, quelque chose comme ça doit sûrement déjà exister en python...

38
demandé sur wim 2013-03-13 20:19:55

11 réponses

la méthode de Newton fonctionne parfaitement sur les entiers:

def isqrt(n):
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

renvoie le plus grand entier x pour qui x * x ne pas dépasser n . Si vous voulez vérifier si le résultat est exactement la racine carrée, simplement effectuer la multiplication pour vérifier si n est un carré parfait.

je discute de cet algorithme, et trois autres algorithmes pour calculer des racines carrées, à mon blog .

64
répondu user448810 2014-06-29 13:04:22

désolé pour la réponse très tardive, je viens de tomber sur cette page. Dans le cas où quelqu'un visite cette page à l'avenir, le module python gmpy2 est conçu pour fonctionner avec de très grandes entrées, et inclut entre autres une fonction racine carrée entière.

exemple:

>>> import gmpy2
>>> gmpy2.isqrt((10**100+1)**2)
mpz(10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001L)
>>> gmpy2.isqrt((10**100+1)**2 - 1)
mpz(10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000L)

accordé, tout aura l'étiquette "mpz", mais mpz sont compatibles avec int's:

>>> gmpy2.mpz(3)*4
mpz(12)

>>> int(gmpy2.mpz(12))
12

voir mon autre réponse pour une analyse du rendement de cette méthode par rapport à d'autres réponses à cette question.

télécharger: https://code.google.com/p/gmpy/

13
répondu mathmandan 2017-05-23 12:17:40

la main à la racine carrée de l'algorithme de

il s'avère qu'il existe un algorithme pour calculer les racines carrées que vous pouvez calculer à la main, quelque chose comme la division longue. Chaque itération de l'algorithme produit un chiffre exactement de la racine carrée tout en consommant deux chiffres du nombre dont la racine carrée que vous cherchez. Alors que la version "longue main" de l'algorithme est spécifié en décimal, il fonctionne dans n'importe quelle base, avec être binaire la plus simple à mettre en œuvre et peut-être la plus rapide à exécuter (selon la représentation de bignum sous-jacente).

parce que cet algorithme fonctionne sur les nombres chiffre par chiffre, il produit des résultats exacts pour les carrés parfaits arbitrairement grands, et pour les non-carrés parfaits, peut produire autant de chiffres de précision (à la droite de la place décimale) que désiré.

il y a deux belles Écritures sur le site" Dr. Math "qui expliquent l'algorithme:

et voici une implémentation en Python:

def exact_sqrt(x):
    """Calculate the square root of an arbitrarily large integer. 

    The result of exact_sqrt(x) is a tuple (a, r) such that a**2 + r = x, where
    a is the largest integer such that a**2 <= x, and r is the "remainder".  If
    x is a perfect square, then r will be zero.

    The algorithm used is the "long-hand square root" algorithm, as described at
    http://mathforum.org/library/drmath/view/52656.html

    Tobin Fricke 2014-04-23
    Max Planck Institute for Gravitational Physics
    Hannover, Germany
    """

    N = 0   # Problem so far
    a = 0   # Solution so far

    # We'll process the number two bits at a time, starting at the MSB
    L = x.bit_length()
    L += (L % 2)          # Round up to the next even number

    for i in xrange(L, -1, -1):

        # Get the next group of two bits
        n = (x >> (2*i)) & 0b11

        # Check whether we can reduce the remainder
        if ((N - a*a) << 2) + n >= (a<<2) + 1:
            b = 1
        else:
            b = 0

        a = (a << 1) | b   # Concatenate the next bit of the solution
        N = (N << 2) | n   # Concatenate the next bit of the problem

    return (a, N-a*a)

, Vous pouvez facilement modifier cette fonction pour effectuer des itérations pour calculer la partie fractionnaire de la racine carrée. J'étais le plus intéressé par des racines de calcul de carrés parfaits grands.

Je ne suis pas sûr de comparer cela à l'algorithme de "méthode de Newton entier". Je soupçonne que la méthode de Newton est plus rapide, car elle peut en principe générer plusieurs bits de la solution en une itération, tandis que l'algorithme "main longue" génère exactement un bit de la solution par itération.

source repo: https://gist.github.com/tobin/11233492

6
répondu nibot 2014-04-24 20:50:24

Voici une mise en œuvre très simple:

def i_sqrt(n):
    i = n.bit_length() >> 1    # i = floor( (1 + floor(log_2(n))) / 2 )
    m = 1 << i    # m = 2^i
    #
    # Fact: (2^(i + 1))^2 > n, so m has at least as many bits 
    # as the floor of the square root of n.
    #
    # Proof: (2^(i+1))^2 = 2^(2i + 2) >= 2^(floor(log_2(n)) + 2)
    # >= 2^(ceil(log_2(n) + 1) >= 2^(log_2(n) + 1) > 2^(log_2(n)) = n. QED.
    #
    while m*m > n:
        m >>= 1
        i -= 1
    for k in xrange(i-1, -1, -1):
        x = m | (1 << k)
        if x*x <= n:
            m = x
    return m

ce n'est qu'une recherche binaire. Initialiser la valeur m pour être la plus grande puissance de 2 qui ne dépasse pas la racine carrée, puis vérifier si chaque plus petit bit peut être réglé tout en gardant le résultat pas plus grand que la racine carrée. (Vérifier les bits un à un, dans l'ordre décroissant.)

pour des valeurs raisonnablement élevées de n (disons, autour de 10**6000 , ou autour de 20000 bits), ce qui semble être:

toutes ces approches réussissent sur des entrées de cette taille, mais sur ma machine, Cette fonction prend environ 1,5 seconde, alors que @Nibot prend environ 0,9 seconde, @user448810 prend environ 19 secondes, et la méthode intégrée gmpy2 prend moins d'une milliseconde(!). Exemple:

>>> import random
>>> import timeit
>>> import gmpy2
>>> r = random.getrandbits
>>> t = timeit.timeit
>>> t('i_sqrt(r(20000))', 'from __main__ import *', number = 5)/5. # This function
1.5102493192883117
>>> t('exact_sqrt(r(20000))', 'from __main__ import *', number = 5)/5. # Nibot
0.8952787937686366
>>> t('isqrt(r(20000))', 'from __main__ import *', number = 5)/5. # user448810
19.326695976676184
>>> t('gmpy2.isqrt(r(20000))', 'from __main__ import *', number = 5)/5. # gmpy2
0.0003599147067689046
>>> all(i_sqrt(n)==isqrt(n)==exact_sqrt(n)[0]==int(gmpy2.isqrt(n)) for n in (r(1500) for i in xrange(1500)))
True

cette fonction peut être généralisée facilement, bien qu'elle ne soit pas tout à fait aussi agréable parce que je n'ai pas tout à fait aussi précis d'une supposition initiale pour m :

def i_root(num, root, report_exactness = True):
    i = num.bit_length() / root
    m = 1 << i
    while m ** root < num:
        m <<= 1
        i += 1
    while m ** root > num:
        m >>= 1
        i -= 1
    for k in xrange(i-1, -1, -1):
        x = m | (1 << k)
        if x ** root <= num:
            m = x
    if report_exactness:
        return m, m ** root == num
    return m

cependant, notez que gmpy2 a aussi une méthode i_root .

en fait, cette méthode pourrait être adaptée et appliquée à n'importe quelle fonction (non négative, croissante) f pour déterminer un "entier inverse de f ". Cependant, pour choisir une valeur initiale efficace de m , vous voulez tout de même savoir quelque chose sur f .

La fonction i_sqrt peut être réécrite pour éviter toute multiplication. cela donne un coup de pouce de performance impressionnant!

def improved_i_sqrt(n):
    assert n >= 0
    if n == 0:
        return 0
    i = n.bit_length() >> 1    # i = floor( (1 + floor(log_2(n))) / 2 )
    m = 1 << i    # m = 2^i
    #
    # Fact: (2^(i + 1))^2 > n, so m has at least as many bits
    # as the floor of the square root of n.
    #
    # Proof: (2^(i+1))^2 = 2^(2i + 2) >= 2^(floor(log_2(n)) + 2)
    # >= 2^(ceil(log_2(n) + 1) >= 2^(log_2(n) + 1) > 2^(log_2(n)) = n. QED.
    #
    while (m << i) > n: # (m<<i) = m*(2^i) = m*m
        m >>= 1
        i -= 1
    d = n - (m << i) # d = n-m^2
    for k in xrange(i-1, -1, -1):
        j = 1 << k
        new_diff = d - (((m<<1) | j) << k) # n-(m+2^k)^2 = n-m^2-2*m*2^k-2^(2k)
        if new_diff >= 0:
            d = new_diff
            m |= j
    return m

noter que par construction, le k TH bit de m << 1 n'est pas réglé, donc bitwise-ou peut être utilisé pour mettre en œuvre l'ajout de (m<<1) + (1<<k) . Finalement j'ai (2*m*(2**k) + 2**(2*k)) écrit comme (((m<<1) | (1<<k)) << k) , donc c'est trois quarts et un bitwise-or (suivi d'une soustraction pour obtenir new_diff ). Peut-être qu'il est encore un moyen plus efficace de faire cela? Quoi qu'il en soit, c'est bien mieux que de multiplier m*m ! Comparer avec ci-dessus:

>>> t('improved_i_sqrt(r(20000))', 'from __main__ import *', number = 5)/5.
0.10908999762373242
>>> all(improved_i_sqrt(n) == i_sqrt(n) for n in xrange(10**6))
True
6
répondu mathmandan 2017-05-23 12:09:39

une option serait d'utiliser le module decimal et de le faire sur des flotteurs suffisamment précis:

import decimal

def isqrt(n):
    nd = decimal.Decimal(n)
    with decimal.localcontext() as ctx:
        ctx.prec = n.bit_length()
        i = int(nd.sqrt())
    if i**2 != n:
        raise ValueError('input was not a perfect square')
    return i

qui je pense devrait fonctionner:

>>> isqrt(1)
1
>>> isqrt(7**14) == 7**7
True
>>> isqrt(11**1000) == 11**500
True
>>> isqrt(11**1000+1)
Traceback (most recent call last):
  File "<ipython-input-121-e80953fb4d8e>", line 1, in <module>
    isqrt(11**1000+1)
  File "<ipython-input-100-dd91f704e2bd>", line 10, in isqrt
    raise ValueError('input was not a perfect square')
ValueError: input was not a perfect square
5
répondu DSM 2013-03-13 16:43:18

semble que vous pourriez vérifier comme ceci:

if int(math.sqrt(n))**2 == n:
    print n, 'is a perfect square'

mise à jour:

comme vous l'avez fait remarquer ci-dessus échoue pour les grandes valeurs de n . Pour ceux-ci, ce qui suit semble prometteur, ce qui est une adaptation du code exemple C, par Martin Guy @ UKC, juin 1985, pour la méthode de calcul numérique binaire à l'apparence relativement simple mentionnée dans L'article de Wikipedia méthodes de calcul des racines carrées :

from math import ceil, log

def isqrt(n):
    res = 0
    bit = 4**int(ceil(log(n, 4))) if n else 0  # smallest power of 4 >= the argument
    while bit:
        if n >= res + bit:
            n -= res + bit
            res = (res >> 1) + bit
        else:
            res >>= 1
        bit >>= 2
    return res

if __name__ == '__main__':
    from math import sqrt  # for comparison purposes

    for i in range(17)+[2**53, (10**100+1)**2]:
        is_perfect_sq = isqrt(i)**2 == i
        print '{:21,d}:  math.sqrt={:12,.7G}, isqrt={:10,d} {}'.format(
            i, sqrt(i), isqrt(i), '(perfect square)' if is_perfect_sq else '')

sortie:

                    0:  math.sqrt=           0, isqrt=         0 (perfect square)
                    1:  math.sqrt=           1, isqrt=         1 (perfect square)
                    2:  math.sqrt=    1.414214, isqrt=         1
                    3:  math.sqrt=    1.732051, isqrt=         1
                    4:  math.sqrt=           2, isqrt=         2 (perfect square)
                    5:  math.sqrt=    2.236068, isqrt=         2
                    6:  math.sqrt=     2.44949, isqrt=         2
                    7:  math.sqrt=    2.645751, isqrt=         2
                    8:  math.sqrt=    2.828427, isqrt=         2
                    9:  math.sqrt=           3, isqrt=         3 (perfect square)
                   10:  math.sqrt=    3.162278, isqrt=         3
                   11:  math.sqrt=    3.316625, isqrt=         3
                   12:  math.sqrt=    3.464102, isqrt=         3
                   13:  math.sqrt=    3.605551, isqrt=         3
                   14:  math.sqrt=    3.741657, isqrt=         3
                   15:  math.sqrt=    3.872983, isqrt=         3
                   16:  math.sqrt=           4, isqrt=         4 (perfect square)
9,007,199,254,740,992:  math.sqrt=9.490627E+07, isqrt=94,906,265
100,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,020,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,001:  math.sqrt=      1E+100, isqrt=10,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,001 (perfect square)
3
répondu martineau 2013-03-14 14:39:26

votre fonction échoue pour les entrées importantes:

In [26]: isqrt((10**100+1)**2)

ValueError: input was not a perfect square

il y a une recette sur le site actif qui devrait être, espérons-le, plus fiable puisqu'elle utilise des maths entières seulement. Il est basé sur une question antérieure de StackOverflow: écrire votre propre fonction de racine carrée

2
répondu NPE 2017-05-23 12:25:40

j'ai trouvé ce fil il y a quelques jours et j'ai réécrit la solution de nibot , et en coupant le nombre d'itérations en deux et en faisant quelques autres petits ajustements de performance, j'ai été capable d'améliorer la performance d'un facteur de ~ 2,4: "15198090920"

def isqrt(n):
    a = 0 # a is the current answer.
    r = 0 # r is the current remainder.
    for s in reversed(range(0, n.bit_length(), 2)): # Shift n by s bits.
        t = n >> s & 3 # t is the two next most significant bits of n.
        r = r << 2 | t # Increase the remainder as if no new bit is set.
        c = a << 2 | 1 # c is an intermediate value used for comparison.
        b = r >= c     # b is the next bit in the remainder.
        if b: 
            r -= c     # b has been set, so reduce the remainder.
        a = a << 1 | b # Update the answer to include b.
    return (a, r)

Voici les résultats de timeit :

>>> timeit('isqrt(12345678901234567890)', setup='from __main__ import isqrt')
8.862877120962366

ensuite, pour comparaison, j'ai mis en œuvre l'algorithme de racine carrée entier le plus couramment utilisé: Newton la méthode . Cette définition est beaucoup plus compacte.

def isqrt(n):
    x, y = n, n >> 1
    while x > y:
        x, y = y, (y + n//y) >> 1
    return (x, n - x*x)

il s'avère que même une version optimisée de racines carrées longhand est plus lente que la méthode de Newton, prenant environ 1,5 fois plus de temps.

>>> timeit('isqrt(12345678901234567890)', setup='from __main__ import isqrt')
5.74083631898975

donc, en conclusion, Si vous avez besoin d'une fonction de racine carrée Python pure rapide, ne cherchez pas plus loin que celui fourni ci-dessus.

Edit: j'ai corrigé le bug dans la méthode de Newton ci-dessus. Sur mon machine, il fonctionne ~10% plus rapide que la solution user448810 .

0
répondu castle-bravo 2017-05-23 12:25:40
Les flotteurs

ne peuvent pas être représentés avec précision sur les ordinateurs. Vous pouvez tester pour un réglage de proximité epsilon à une petite valeur dans la précision des flotteurs de python.

def isqrt(n):
    epsilon = .00000000001
    i = int(n**.5 + 0.5)
    if abs(i**2 - n) < epsilon:
        return i
    raise ValueError('input was not a perfect square')
-2
répondu Octipi 2015-12-27 16:27:10

j'ai comparé les différentes méthodes données ici avec une boucle:

for i in range (1000000): # 700 msec
    r=int(123456781234567**0.5+0.5)
    if r**2==123456781234567:rr=r
    else:rr=-1

constatant que celui-ci est le plus rapide et n'a pas besoin d'importation mathématique. Très long pourrait échouer, mais regardez ce

15241576832799734552675677489**0.5 = 123456781234567.0
-2
répondu Knut Ångström 2016-12-13 16:33:34

essayer cette condition (pas de calcul supplémentaire):

def isqrt(n):
  i = math.sqrt(n)
  if i != int(i):
    raise ValueError('input was not a perfect square')  
  return i

si vous en avez besoin pour retourner un int (pas un float avec un zéro de queue), alors Assignez une 2e variable ou calculez int(i) deux fois.

-4
répondu javex 2015-12-27 16:26:00