如何计算 Python 的平方根?

How do I calculate square root in Python?

我需要计算一些数字的平方根,例如√9 = 3√2 = 1.4142。我怎样才能在 Python 中做到这一点?

输入可能都是正整数,并且相对较小(比如小于十亿),但如果不是,是否有任何可能会损坏的东西?


相关

注意:这是对canonical question after a discussion on Meta about an existing question with the same title.[的尝试=25=]

选项 1:math.sqrt()

标准库中的 math 模块将 a sqrt function to calculate the square root of a number. It takes any type that can be converted to float(包括 int)作为参数,并且 return 是一个 float.

>>> import math
>>> math.sqrt(9)
3.0

选项 2:分数指数

The power operator (**) or the built-in pow() function can also be used to calculate a square root. Mathematically speaking, the square root of a equals a to the power of 1/2.

幂运算符需要数字类型并匹配 the conversion rules for binary arithmetic operators,因此在本例中它将 return 为 floatcomplex 数字。

>>> 9 ** (1/2)
3.0
>>> 9 ** .5  # Same thing
3.0
>>> 2 ** .5
1.4142135623730951

(注意:在 Python 2 中,1/2 被截断为 0,因此您必须使用 1.0/2 或类似的强制浮点运算。参见 Why does Python give the "wrong" answer for square root?)

此方法可以推广到 nth root,但不能精确表示为 float 的分数(例如 1/3 或任何不是 2 的幂的分母)可能会导致一些不准确的地方:

>>> 8 ** (1/3)
2.0
>>> 125 ** (1/3)
4.999999999999999

边缘情况

消极且复杂

求幂适用于负数和复数,但结果略有不准确:

>>> (-25) ** .5  # Should be 5j
(3.061616997868383e-16+5j)
>>> 8j ** .5  # Should be 2+2j
(2.0000000000000004+2j)

注意 -25 上的括号!否则它被解析为 -(25**.5) 因为 exponentiation is more tightly binding than unary negation.

与此同时,math 仅适用于浮点数,因此对于 x<0math.sqrt() 将提高 ValueError: math domain error,对于复杂的 x,它将提高 TypeError: can't convert complex to float。相反,您可以使用 cmath.sqrt(),它比取幂更准确(并且可能也更快):

>>> import cmath
>>> cmath.sqrt(-25)
5j
>>> cmath.sqrt(8j)
(2+2j)

精度

这两个选项都涉及到 float 的隐式转换,因此 。例如:

>>> n = 10**30
>>> square = n**2
>>> x = square**.5
>>> x == n
False
>>> x - n  # how far off are they?
0.0
>>> int(x) - n  # how far off is the float from the int?
19884624838656

非常大的数字甚至可能放不下一个浮点数,您会得到 OverflowError: int too large to convert to float。参见 Python sqrt limit for very large numbers?

其他类型

我们以Decimal为例:

求幂失败,除非指数也是 Decimal:

>>> decimal.Decimal('9') ** .5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ** or pow(): 'decimal.Decimal' and 'float'
>>> decimal.Decimal('9') ** decimal.Decimal('.5')
Decimal('3.000000000000000000000000000')

与此同时,mathcmath 会默默地将它们的参数分别转换为 floatcomplex,这可能意味着精度损失。

decimal也有自己的.sqrt(). See also calculating n-th roots using Python 3's decimal module

NumPy

>>> import numpy as np
>>> np.sqrt(25)
5.0
>>> np.sqrt([2, 3, 4])
array([1.41421356, 1.73205081, 2.        ])

docs

否定

对于负实数,它将 return nan,因此 np.lib.scimath.sqrt() 可用于这种情况。

>>> a = np.array([4, -1, np.inf])
>>> np.sqrt(a)
<stdin>:1: RuntimeWarning: invalid value encountered in sqrt
array([ 2., nan, inf])
>>> np.lib.scimath.sqrt(a)
array([ 2.+0.j,  0.+1.j, inf+0.j])

当然,另一种选择是先转换为复数:

>>> a = a.astype(complex)
>>> np.sqrt(a)
array([ 2.+0.j,  0.+1.j, inf+0.j])

SymPy

根据您的目标,尽可能延迟平方根的计算可能是个好主意。 SymPy 可能会有帮助。

SymPy is a Python library for symbolic mathematics.

import sympy
sympy.sqrt(2)
# => sqrt(2)

乍一看这似乎没什么用。

但是 sympy 可以提供比浮点数或小数更多的信息:

sympy.sqrt(8) / sympy.sqrt(27)
# => 2*sqrt(6)/9

此外,没有精度损失。 (√2)² 仍然是一个整数:

s = sympy.sqrt(2)
s**2
# => 2
type(s**2)
#=> <class 'sympy.core.numbers.Integer'>

相比之下,浮点数和小数将 return 一个非常接近 2 但不等于 2 的数字:

(2**0.5)**2
# => 2.0000000000000004

from decimal import Decimal
(Decimal('2')**Decimal('0.5'))**Decimal('2')
# => Decimal('1.999999999999999999999999999')

Sympy 还可以理解更复杂的示例,例如 the Gaussian integral:

from sympy import Symbol, integrate, pi, sqrt, exp, oo
x = Symbol('x')
integrate(exp(-x**2), (x, -oo, oo))
# => sqrt(pi)
integrate(exp(-x**2), (x, -oo, oo)) == sqrt(pi)
# => True

最后,如果需要十进制表示,可以要求比以往任何时候都需要的更多的数字:

sympy.N(sympy.sqrt(2), 1_000_000)
# => 1.4142135623730950488016...........2044193016904841204

二分查找

免责声明:这是针对更专业的 use-case。此方法可能并非在所有情况下都实用。

好处:

  • 可以找到整数值(即哪个 整数 是根?)
  • 无需转换为浮点数,精度更高(也可以做到)

我个人为加密 CTF 挑战(RSA 立方根攻击)实现了这个,我需要一个精确的整数值。

大意可以推广到任何其他根。

def int_squareroot(d: int) -> tuple[int, bool]:
    """Try calculating integer squareroot and return if it's exact"""
    left, right = 1, (d+1)//2
    while left<right-1:
        x = (left+right)//2
        if x**2 > d:
            left, right = left, x
        else:
            left, right = x, right
    return left, left**2==d

编辑:

正如@wjandrea 也指出的那样,**此示例代码无法计算**。这是一个 side-effect 事实,它不会将任何东西转换为浮点数,因此不会丢失精度。如果根是一个整数,你会得到它。如果不是,您将得到最大的数字,其平方小于您的数字。我更新了代码,以便它也 returns 一个 bool 指示值是否正确,并且还修复了导致它无限循环的问题(@wjandrea 也指出)。这种通用方法的实现对于较小的数字仍然有点奇怪,但在 10 以上时我没有问题。

克服此 method/implementation 的问题和限制:

对于较小的数字,您可以使用其他答案中的所有其他方法。他们通常使用浮点数,这 可能 会损失精度,但对于小整数应该没有问题。所有这些使用浮点数的方法都具有与此相同(或几乎相同)的限制。

如果您仍想使用此方法并获得浮点数结果,将其转换为使用浮点数应该也很简单。请注意,这将重新引入精度损失,这是此方法相对于其他方法的独特优势,在这种情况下,您也可以使用任何其他答案。我觉得牛顿法版本收敛的快一点,但我不确定。

对于较大的数字,浮点数会导致精度损失,此方法可以给出更接近实际答案的结果(取决于输入有多大)。如果您想在此范围内使用 non-integers,您也可以使用其他类型,例如此方法中的固定精度数字。

编辑 2,关于其他答案:

目前,afaik,唯一与此实现具有相似或更好的大数字精度的其他答案是 Eric Duminil 提出的 SymPy。该版本也更易于使用,适用于任何类型的数字,唯一的缺点是它需要 SymPy。如果您正在寻找的话,我的实现没有任何巨大的依赖性。

Python的fractions模块及其class、Fraction,实现有理数算术。 Fraction class 没有实现平方根运算,因为大多数平方根都是无理数。但是,它可用于以任意精度逼近平方根,因为 Fraction 的分子和分母是 arbitrary-precision 整数。

下面的方法取正数x和迭代次数,returns上下界为x的平方根。

from fractions import Fraction

def sqrt(x, n):
    x = x if isinstance(x, Fraction) else Fraction(x)
    upper = x + 1
    for i in range(0, n):
        upper = (upper + x/upper) / 2
    lower = x / upper
    if lower > upper:
        raise ValueError("Sanity check failed")
    return (lower, upper)

有关此操作实施的详细信息,请参阅下面的参考资料。它还展示了如何使用上限和下限实现其他操作(尽管那里的 log 操作显然至少有一个错误)。

  • Daumas, M., Lester, D., Muñoz, C.,“验证实数计算:区间算术库”,arXiv:0708.3721 [cs.MS],2007 年。

牛顿法

计算平方根最简单准确的方法是牛顿法。

你有一个数字,你想计算它的平方根 (num),你猜它的平方根 (estimate)。估计可以是任何大于 0 的数字,但有意义的数字会显着缩短递归调用深度。

new_estimate = (estimate + num/estimate) / 2

此行使用这 2 个参数计算出更准确的估计值。您可以将 new_estimate 值传递给函数并计算另一个 new_estimate ,它比前一个更准确,或者您可以像这样进行递归函数定义。

def newtons_method(num, estimate):
    # Computing a new_estimate
    new_estimate = (estimate + num/estimate) / 2
    print(new_estimate)
    # Base Case: Comparing our estimate with built-in functions value
    if new_estimate == math.sqrt(num):
        return True
    else:
        return newtons_method(num, new_estimate)

例如,我们需要找到 30 的平方根。我们知道结果在5到6之间

newtons_method(30,5)

人数是30,估计是5。每次递归调用的结果是:

5.5
5.477272727272727
5.4772255752546215
5.477225575051661

最后的结果是最准确的数字平方根计算。它与 built-in 函数 math.sqrt().

的值相同

这个答案是 originally posted by gunesevitan,但现在被删除了。

任意精度平方根

此变体使用字符串操作将表示十进制 floating-point 数字的字符串转换为 int,调用 math.isqrt 进行实际的平方根提取,然后格式化结果为十进制字符串。 math.isqrt 向下舍入,因此所有生成的数字都是正确的。

输入字符串 num 必须使用普通浮点格式:不支持 'e' 表示法。 num 字符串可以是普通整数,忽略前导零。

digits参数指定结果字符串中的小数位数,即小数点后的位数。

from math import isqrt

def str_sqrt(num, digits):
    """ Arbitrary precision square root

        num arg must be a string
        Return a string with `digits` after
        the decimal point

        Written by PM 2Ring 2022.01.26
    """

    int_part , _, frac_part = num.partition('.')
    num = int_part + frac_part

    # Determine the required precision
    width = 2 * digits - len(frac_part)

    # Truncate or pad with zeroes
    num = num[:width] if width < 0 else num + '0' * width
    s = str(isqrt(int(num)))

    if digits:
        # Pad, if necessary
        s = '0' * (1 + digits - len(s)) + s
        s = f"{s[:-digits]}.{s[-digits:]}"
    return s

测试

print(str_sqrt("2.0", 30))

输出

1.414213562373095048801688724209

对于少量数字,使用 decimal.Decimal.sqrt 速度更快。大约 32 位数字左右,str_sqrtDecimal.sqrt 的速度大致相同。但是在 128 位时,str_sqrtDecimal.sqrt 快 2.2 倍,在 512 位时,它快 4.3 倍,在 8192 位时,它快 7.4 倍。

这是 SageMathCell 服务器上的 live version 运行。

# find square-root of a number
while True:
  num = int(input("Enter a number:\n>>"))
  for i in range(2, num):
    cond = (num % i == 0)
    if(cond == True):
      # print(I)
      k = (i*i == num)
      if(k == True):
        print("Square root of", num, "==>", i)
        break
    else:
      kd = (num**0.5)   # (num**(1/2))
      print("Square root of", num, "==>", kd)

输出:-

Enter a number: 24
Square root of 24 ==> 4.898979485566356
Enter a number: 36
Square root of 36 ==> 6
Enter a number: 49
Square root of 49 ==> 7