在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 ......
11 回答
牛顿方法在整数上运行得非常好:
这将返回x * x不超过n的最大整数x . 如果要检查结果是否完全是平方根,只需执行乘法以检查n是否为完美平方 .
我在my blog讨论了这个算法以及另外三种计算平方根的算法 .
很抱歉很晚才回复;我只是偶然发现了这个页面 . 如果将来有人访问此页面,python模块gmpy2旨在处理非常大的输入,并包括整数平方根函数 .
例:
当然,一切都将有“mpz”标签,但mpz与int的兼容:
有关此方法相对于此问题的其他答案的性能的讨论,请参阅my other answer .
下载:https://code.google.com/p/gmpy/
Long-hand square root algorithm
事实证明,有一种计算平方根的算法,您可以手动计算,例如长除法 . 算法的每次迭代都会生成所得到的平方根的正好一位数,同时消耗您寻找的平方根的数字的两位数 . 虽然算法的“长手”版本以十进制形式指定,但它适用于任何基础,二进制最简单的实现,也许执行速度最快(取决于底层的bignum表示) .
因为该算法逐个数字地对数字进行操作,所以它为任意大的正方形产生精确的结果,对于非完美的正方形,可以根据需要产生尽可能多的精度数字(在小数点的右侧) .
“数学博士”网站上有两篇很好的文章解释了这个算法:
Square Roots in Binary
Longhand Square Roots
这是Python中的一个实现:
您可以轻松修改此函数以执行其他迭代来计算平方根的小数部分 . 我最感兴趣的是计算大型完美正方形的根 .
我不确定这与“整数牛顿方法”算法相比如何 . 我怀疑Newton的方法更快,因为它原则上可以在一次迭代中生成解决方案的多个位,而“长手”算法每次迭代生成解决方案的一个位 .
来源回购:https://gist.github.com/tobin/11233492
这是一个非常简单的实现:
这只是一个二分搜索 . 将值
m
初始化为2的最大幂,不超过平方根,然后检查是否可以设置每个较小的位,同时保持结果不大于平方根 . (按降序一次检查一位 . )对于相当大的
n
值(例如,大约10**6000
,或大约20000
位),这似乎是:比牛顿的方法实现更快described by user448810 .
比my other answer中的
gmpy2
内置方法慢得多 .与Longhand Square Root described by nibot相比,但有点慢 .
所有这些方法都在这个尺寸的输入上成功,但在我的机器上,这个功能需要大约1.5秒,而@Nibot需要大约0.9秒,@ user448810需要大约19秒,而gmpy2内置方法需要不到一毫秒(!) . 例:
这个函数可以很容易地推广,尽管's not quite as nice because I don'对于
m
的初始猜测非常精确:但请注意
gmpy2
也有i_root
方法 .实际上,该方法可以适用于任何(非负的,增加的)函数
f
以确定“f
的整数反转” . 但是,要选择m
的有效初始值,您仍然需要了解f
.Edit: Thanks to @Greggo for pointing out that the i_sqrt function can be rewritten to avoid using any multiplications. 这会带来令人印象深刻的性能提升!
注意,通过构造,
m << 1
的k
未设置,所以按位 - 或者可以用来实现(m<<1) + (1<<k)
的添加 . 最终我将(2*m*(2**k) + 2**(2*k))
写为(((m<<1) | (1<<k)) << k)
,因此它是三个移位和一个按位 - 或(后面跟一个减法得到new_diff
) . 也许还有更有效的方法来获得这个?无论如何,它远胜于倍增m*m
!与上述比较:一种选择是使用
decimal
模块,并在足够精确的浮点数中执行:我认为应该工作:
我在这里对小(0 ... 222)和大(250001)输入的每个(正确)函数进行了基准测试 . 两个案例中明显的获胜者首先是gmpy2.isqrt suggested by mathmandan,其次是ActiveState recipe linked by NPE . ActiveState配方有一堆可以由shift替换的分区,这使得它更快(但仍落后于
gmpy2.isqrt
):基准测试结果:
gmpy2.isqrt() (mathmandan):0.08μs小,0.07 ms大
int(gmpy2.isqrt())
*:0.3μs小,0.07 ms大ActiveState (optimized as above) :0.6μs小,17.0 ms大
ActiveState (npe):1.0μs小,17.3 ms大
castlebravo long-hand:4μs小,80 ms大
mathmandan improved:2.7μs小,120 ms大
martineau(带this correction):2.3μs小,140 ms大
nibot:8μs小,1000 ms大
mathmandan:1.8μs小,2200 ms大
castlebravo Newton’s method:1.5μs小,19000 ms大
user448810:1.4μs小,20000 ms大
(*由于
gmpy2.isqrt
返回gmpy2.mpz
对象,其行为大多数但不完全像int
,您可能需要将其转换回int
用于某些用途 . )好像你可以像这样检查:
更新:
正如您所指出的那样,上面的
n
值很大 . 对于那些看起来很有希望的人,这是1985年6月Martin Guy @ UKC的示例C代码的改编,用于维基百科文章_2474957中提到的相对简单的二进制数字逐位计算方法:输出:
您的功能因大输入而失败:
有一个recipe on the ActiveState site应该更可靠,因为它只使用整数数学 . 它基于早期的StackOverflow问题:Writing your own square root function
浮动无法在计算机上精确显示 . 您可以在python浮点数的精度范围内测试所需的接近度设置epsilon到一个小值 .
我将这里给出的不同方法与循环进行了比较:
发现这个是最快的,不需要数学导入 . 很长时间可能会失败,但看看这个
尝试这种情况(无需额外计算):
如果你需要它返回一个
int
(不是带有尾随零的float
),那么要么分配第二个变量,要么计算int(i)
两次 .