python或标准库中是否存在整数平方根?我希望它是精确的(即返回一个整数),并且如果没有解决方案则吠叫。
此刻,我推出了自己的天真的一个:
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 ......
答案 0 :(得分:74)
Newton的方法在整数上非常有效:
def isqrt(n):
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
这将返回 x * x 不超过 n 的最大整数 x 。如果要检查结果是否完全是平方根,只需执行乘法检查 n 是否为正方形。
我在my blog讨论了这个算法以及另外三种计算平方根的算法。
答案 1 :(得分:16)
很抱歉很晚才回复;我只是偶然发现了这个页面。如果将来有人访问此页面,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。
答案 2 :(得分:7)
这是一个非常简单的实现:
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的最大幂,不超过平方根,然后检查是否可以设置每个较小的位,同时保持结果不大于平方根。 (按降序一次检查一位。)
对于相当大的n
值(例如,大约10**6000
或大约20000
位),这似乎是:
gmpy2
内置方法慢得多。所有这些方法都可以在这种尺寸的输入上成功,但在我的机器上,此功能大约需要1.5秒,而@Nibot需要大约0.9秒,@ user448810需要大约19秒,并且gmpy2内置方法花费不到一毫秒(!)。例如:
>>> 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
&#34;的整数反函数。但是,要选择m
的有效初始值,您仍然希望了解f
的内容。
编辑:感谢@Greggo指出可以重写i_sqrt
函数以避免使用任何乘法。这会带来令人印象深刻的性能提升!
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
请注意,通过构造,k
的{{1}}位未设置,因此按位或可用于实现m << 1
的添加。最后我将(m<<1) + (1<<k)
写为(2*m*(2**k) + 2**(2*k))
,因此它有三个班次和一个按位 - 或者(后面跟一个减法来获得(((m<<1) | (1<<k)) << k)
)。也许还有更有效的方法来获得这个?无论如何,它比乘以new_diff
要好得多!与上述相比:
m*m
答案 3 :(得分:6)
长方形根算法
事实证明,有一种计算平方根的算法可以手工计算,比如长除法。算法的每次迭代都会生成所得到的平方根的正好一位数,同时消耗您寻找的平方根的数字的两位数。虽然算法的“长手”版本以十进制形式指定,但它可以在任何基础上工作,二进制文件最容易实现,也许执行速度最快(取决于底层的bignum表示)。
因为这个算法对数字进行逐位运算,所以它可以为任意大的正方形生成精确的结果,对于非正方形,可以产生尽可能多的精度数字(在小数点的右边)。期望的。
“数学博士”网站上有两篇很好的文章解释了算法:
这是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)
您可以轻松修改此函数以执行其他迭代来计算平方根的小数部分。我最感兴趣的是计算大型完美广场的根源。
我不确定这与“整数牛顿方法”算法相比如何。我怀疑牛顿的方法更快,因为它原则上可以在一次迭代中生成解决方案的多个位,而“长手”算法每次迭代只生成一个解决方案。
答案 4 :(得分:5)
一种选择是使用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 :(得分:5)
我在这里分别以小(0…2 22 )和大(2 50001 )输入为基准(正确)。两种情况下的明显赢家都是gmpy2.isqrt
suggested by mathmandan,其次是ActiveState recipe linked by NPE。 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()
(mathmandan):小0.08 µs,大0.07 ms int(gmpy2.isqrt())
*:小0.3 µs,大0.07 ms (*由于gmpy2.isqrt
返回一个gmpy2.mpz
对象,该对象的行为与int
大致相同,但不完全相同,因此您可能需要将其转换回int
用途。)
答案 6 :(得分:3)
好像你可以这样检查:
if int(math.sqrt(n))**2 == n:
print n, 'is a perfect square'
更新
正如您所指出,上述n
的大值失败。对于那些看起来很有希望的人,这是1985年6月Martin Guy @ UKC的示例C代码的改编,用于维基百科文章Methods of computing square roots中提到的相对简单的二进制数字逐位数计算方法。 :
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 '')
输出:
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)
答案 7 :(得分:1)
您的功能因大输入而失败:
In [26]: isqrt((10**100+1)**2)
ValueError: input was not a perfect square
有一个recipe on the ActiveState site应该更可靠,因为它只使用整数数学。它基于早期的StackOverflow问题:Writing your own square root function
答案 8 :(得分:1)
答案 9 :(得分:-2)
浮动无法在计算机上精确显示。您可以在python浮点数的精度范围内测试所需的接近度设置epsilon到一个小值。
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')
答案 10 :(得分:-2)
我将这里给出的不同方法与循环进行了比较:
for i in range (1000000): # 700 msec
r=int(123456781234567**0.5+0.5)
if r**2==123456781234567:rr=r
else:rr=-1
发现这个是最快的,不需要数学导入。很长时间可能会失败,但看看这个
15241576832799734552675677489**0.5 = 123456781234567.0
答案 11 :(得分:-4)
尝试此条件(无需额外计算):
def isqrt(n):
i = math.sqrt(n)
if i != int(i):
raise ValueError('input was not a perfect square')
return i
如果您需要它返回int
(不是float
,尾随零),则分配第二个变量或计算int(i)
两次。