web-dev-qa-db-ja.com

pythonの整数平方根

Pythonまたは標準ライブラリのどこかに整数平方根がありますか?私はそれを正確にしたい(つまり整数を返したい)、そして解決策がない場合はbarえたい。

現時点では、私は自分の素朴なものを転がしました:

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

しかし、それは見苦しく、大きな整数については本当に信用していません。値を超えた場合、正方形を反復処理してあきらめることができますが、そのようなことをするのは少し遅いと思います。また、私はおそらく車輪を再発明していると思います、このようなものはきっとpython既に...

47
wim

ニュートンの方法は整数に対して完全にうまく機能します。

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

これは、最大の整数xを返しますx*xnを超えません。結果が正確に平方根であるかどうかを確認する場合は、乗算を実行してnが完全な平方かどうかを確認します。

my blog で、このアルゴリズムと平方根を計算するための他の3つのアルゴリズムについて説明します。

77
user448810

非常に遅い応答でごめんなさい。私はこのページにつまずいた。将来このページにアクセスする場合、pythonモジュールgmpy2は非常に大きな入力で動作するように設計されており、特に整数平方根関数が含まれています。

例:

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

確かに、すべてに「mpz」タグがありますが、mpzはintと互換性があります。

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

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

この質問に対する他のいくつかの回答に対するこのメソッドのパフォーマンスの説明については、 my other answer を参照してください。

ダウンロード: https://code.google.com/p/gmpy/

16
mathmandan

これは非常に簡単な実装です。

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

これは単なるバイナリ検索です。値mを平方根を超えない最大の2のべき乗になるように初期化し、平方根より大きくない結果を維持しながら各小さいビットを設定できるかどうかを確認します。 (ビットを降順で1つずつチェックします。)

nのかなり大きな値(たとえば、10**6000前後、または20000前後)の場合、これは次のように思われます。

これらのアプローチはすべて、このサイズの入力で成功しますが、私のマシンでは、この関数は約1.5秒かかりますが、@ Nibotは約0.9秒、@ user448810は約19秒、gmpy2組み込みメソッドは1ミリ秒未満かかります(!)。例:

>>> 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

この関数は簡単に一般化できますが、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

ただし、gmpy2にはi_rootメソッドもあります。

実際、このメソッドを任意の(非負、増加)関数fに適用および適用して、「fの整数逆数」を決定できます。ただし、mの効率的な初期値を選択するには、fについて何かを知りたいと思うでしょう。

編集:乗算を使用しないようにi_sqrt関数を書き換えられることを指摘してくれた@Greggoに感謝します。これにより、パフォーマンスが大幅に向上します。

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

構造上、m << 1kthビットは設定されていないため、bitwise-orを使用して(m<<1) + (1<<k)の追加を実装できます。最終的に(2*m*(2**k) + 2**(2*k))として(((m<<1) | (1<<k)) << k)と書かれているので、3つのシフトと1つのビット単位のORです(引き続いてnew_diffを取得します)。これを取得するより効率的な方法はまだありますか?とにかく、m*mを乗算するよりもはるかに優れています!上記と比較してください:

>>> 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

ここでは、小さい(0…2)のすべての(正しい)関数をベンチマークしました22)および大(250001)入力。どちらの場合でも明確な勝者は、最初に _gmpy2.isqrt_がmathmandanによって提案された で、その後に NPEによってリンクされたActiveStateレシピ が続きます。 ActiveStateレシピには、シフトで置き換えることができる多くの部門があり、少し高速になります(ただし、まだ_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")
_

ベンチマーク結果:

(* _gmpy2.isqrt_は_gmpy2.mpz_オブジェクトを返すので、ほとんどがintのように動作するわけではありませんが、一部の用途ではintに戻す必要があります。

7
Anders Kaseorg

ロングハンド平方根アルゴリズム

手作業で計算できる平方根を計算するためのアルゴリズムがあります。これは、長期分割のようなものです。アルゴリズムの各反復は、結果として平方根の正確に1桁を生成し、平方根を求める数字の2桁を消費します。アルゴリズムの「ロングハンド」バージョンは10進数で指定されますが、任意のベースで機能します。バイナリは実装が最も簡単で、おそらく実行が最も高速です(基になるbignum表現によって異なります)。

このアルゴリズムは数字ごとに数字を操作するため、任意の大きな完全な正方形に対して正確な結果を生成し、非完全な正方形に対しては、必要な桁数(小数点以下の桁数)の精度を生成できます。

「Dr. Math」サイトには、アルゴリズムを説明する2つの素晴らしい記事があります。

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)

この関数を簡単に変更して追加の反復を実行し、平方根の小数部を計算できます。私は、大きな完全な正方形の根を計算することに最も興味がありました。

これが「整数ニュートン法」アルゴリズムとどのように比較されるかわかりません。 「ロングハンド」アルゴリズムは反復ごとに正確に1ビットの解を生成するのに対し、Newtonの方法は原則として1回の反復で複数ビットの解を生成できるため、Newtonの方が高速であると思われます。

ソースリポジトリ: https://Gist.github.com/tobin/11233492

6
nibot

1つのオプションはdecimalモジュールを使用し、十分に正確なフロートで実行することです。

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

私はそれがうまくいくと思う:

>>> 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

このように確認できるようです:

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

更新:

指摘したように、nの大きな値に対して上記は失敗します。これらについては、ウィキペディアの記事で言及されている比較的単純な2進数の数字ごとの計算方法について、Martin Guy @ UKCによる1985年6月のサンプルCコードの適応である、以下が有望に見えます Methods平方根の計算

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 '')

出力:

                    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

入力が大きい場合、関数は失敗します。

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

ValueError: input was not a perfect square

ActiveStateサイトのレシピ があります。これは、整数演算のみを使用するため、より信頼性が高いはずです。以前のStackOverflowの質問に基づいています: 独自の平方根関数の作成

1
NPE