web-dev-qa-db-fra.com

Racine carrée entière en python

Existe-t-il une racine carrée entière quelque part en python ou dans les bibliothèques standard? Je veux que ce soit exact (c'est-à-dire retourner un entier) et aboyer s'il n'y a pas de solution.

En ce moment, 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 moche et je ne lui fais pas vraiment confiance pour les grands nombres entiers. Je pourrais parcourir les carrés et abandonner si j'ai dépassé la valeur, mais je suppose que ce serait un peu lent de faire quelque chose comme ça. Aussi, je suppose que je réinventerais probablement la roue, quelque chose comme ça doit sûrement exister dans python déjà ...

47
wim

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

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

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

77
user448810

Désolé pour la réponse très tardive; Je suis juste tombé sur cette page. Dans le cas où quelqu'un visiterait cette page à l'avenir, le module python gmpy2 est conçu pour fonctionner avec de très grandes entrées, et comprend entre autres une fonction de 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)

Certes, tout aura la balise "mpz", mais les mpz sont compatibles avec les int:

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

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

Voir mon autre réponse pour une discussion des performances de cette méthode par rapport à d'autres réponses à cette question.

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

16
mathmandan

Voici une implémentation 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

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

Pour des valeurs raisonnablement grandes de n (disons autour de 10**6000 ou environ 20000 bits), cela 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, tandis que celle de @ Nibot prend environ 0,9 seconde, celle de @ 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 aussi agréable que Nice car je n'ai pas une précision initiale aussi précise 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 également un i_root méthode.

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 voudriez toujours savoir quelque chose sur f.

Edit: Merci à @Greggo d'avoir souligné que le i_sqrt La fonction peut être réécrite pour éviter d'utiliser des multiplications. Cela donne une amélioration impressionnante des performances!

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

Notez que par construction, le kème bit de m << 1 n'est pas défini, donc au niveau du bit ou peut être utilisé pour implémenter l'ajout de (m<<1) + (1<<k). En fin de compte, j'ai (2*m*(2**k) + 2**(2*k)) écrit comme (((m<<1) | (1<<k)) << k), il s'agit donc de trois décalages et d'un bit ou (suivi d'une soustraction pour obtenir new_diff). Peut-être existe-t-il encore un moyen plus efficace d'obtenir cela? Quoi qu'il en soit, c'est bien mieux que de multiplier m*m! Comparez 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
7
mathmandan

J'ai comparé chaque fonction (correcte) ici sur les deux petites (0… 222) et grand (250001) contributions. Les gagnants clairs dans les deux cas sont gmpy2.isqrt Suggéré par mathmandan en premier lieu, suivi de recette ActiveState liée par NPE en deuxième. La recette ActiveState a un tas de divisions qui peuvent être remplacées par des changements, ce qui la rend un peu plus rapide (mais toujours derrière gmpy2.isqrt):

def isqrt(n):
    if n > 0:
        x = 1 << (n.bit_length() + 1 >> 1)
        while True:
            y = (x + n // x) >> 1
            if y >= x:
                return x
            x = y
    Elif n == 0:
        return 0
    else:
        raise ValueError("square root not defined for negative numbers")

Résultats de référence:

(* Puisque gmpy2.isqrt Renvoie un objet gmpy2.mpz, Qui se comporte principalement mais pas exactement comme un int, vous devrez peut-être le reconvertir en int pour certaines utilisations.)

7
Anders Kaseorg

Algorithme de racine carrée à long terme

Il s'avère qu'il existe un algorithme de calcul des racines carrées que vous pouvez calculer à la main, quelque chose comme une longue division. Chaque itération de l'algorithme produit exactement un chiffre de la racine carrée résultante tout en consommant deux chiffres du nombre dont vous recherchez la racine carrée. Bien que la version "longue" de l'algorithme soit spécifiée en décimal, elle fonctionne dans n'importe quelle base, le binaire étant le plus simple à implémenter et peut-être le plus rapide à exécuter (en fonction de la représentation sous-jacente du bignum).

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

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 supplémentaires afin de calculer la partie fractionnaire de la racine carrée. J'étais plus intéressé par le calcul des racines de grands carrés parfaits.

Je ne sais pas comment cela se compare à l'algorithme de la "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 seule itération, tandis que l'algorithme "long hand" génère exactement un bit de la solution par itération.

Repo source: https://Gist.github.com/tobin/11233492

6
nibot

Une option serait d'utiliser le module decimal, et de le faire dans des flottants 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
DSM

On dirait que vous pouvez vérifier comme ceci:

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

Mise à jour:

Comme vous l'avez souligné, ce qui précède échoue pour les grandes valeurs de n. Pour ceux-ci, ce qui suit semble prometteur, qui est une adaptation de l'exemple de code C, par Martin Guy @ UKC, juin 1985, pour la méthode de calcul numérique par chiffre binaire relativement simple mentionnée dans l'article de Wikipédia 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 __== '__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)
4
martineau

Votre fonction échoue pour les grandes entrées:

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

ValueError: input was not a perfect square

Il y a un recette sur le site ActiveState qui devrait être plus fiable car il utilise uniquement des mathématiques entières. Il est basé sur une question précédente de StackOverflow: Écriture de votre propre fonction de racine carrée

1
NPE