在Python中如何计算平方根?

49
我需要计算一些数字的平方根,例如√9 = 3和√2 = 1.4142。我该如何在Python中实现?
输入可能是所有正整数,并且相对较小(比如小于十亿),但以防万一,有什么可能会出错的地方吗?
注意:这是在关于现有问题的讨论之后,在规范问题上的一次尝试。
相关问题:
- 在Python中求整数平方根 - 如何找到整数的第n个根? - Python中有没有x的第n个根的简写? - **(1/2)、math.sqrt和cmath.sqrt之间的区别? - 为什么对于大数,math.sqrt()是不正确的? - 对于非常大的数,Python的sqrt有限制吗? - Python 3中大于10^2000的数的平方根 - 在Python中,x**.5和math.sqrt(x)哪个更快? - 为什么Python给出了错误的平方根答案?(特定于Python 2)
- 使用Python 3的decimal模块计算n次根 - 如何使用Python计算-1的平方根?(专注于NumPy)
- 平方根的任意精度

1
评论不是用于长时间讨论的;此对话已被移至聊天室 - Stephen Rauch
9个回答

81
选项1: math.sqrt() 标准库中的math模块有一个sqrt函数,用于计算一个数的平方根。它接受任何可以转换为float的类型(包括int),并返回一个float
>>> import math
>>> math.sqrt(9)
3.0

选项2:分数指数

可以使用幂运算符(**或内置的pow()函数来计算平方根。从数学角度来看,数值a的平方根等于a1/2次幂。

幂运算符要求使用数值类型,并符合二进制算术运算符的转换规则,因此在这种情况下,它将返回一个floatcomplex数。

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

(注意:在Python 2中,1/2会被截断为0,所以你必须使用1.0/2或类似的方法来强制使用浮点数运算。参见为什么Python计算平方根时会给出“错误”的答案?
(这种方法可以推广到开n次方根,但是不能精确表示为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),因为指数运算比取反运算的优先级更高。)
与此同时,math只适用于浮点数,所以对于x<0math.sqrt(x)会引发ValueError: math domain error,对于复数x,它会引发TypeError: can't convert complex to float。相反,你可以使用cmath.sqrt(x),它比指数运算更准确(而且可能更快):
>>> import cmath
>>> cmath.sqrt(-25)
5j
>>> cmath.sqrt(8j)
(2+2j)

精度

这两个选项都涉及到将数据隐式转换为float,所以浮点数精度是一个因素。例如,让我们尝试一个大数:

>>> n = 10**30
>>> x = n**2
>>> root = x**.5
>>> root == n
False
>>> root - n  # how far off are they?
0.0
>>> int(root) - 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()。另请参阅使用Python 3的decimal模块计算n次根

3
指数运算适用于负数和复数,尽管结果会有些微差,但我不确定原因。这是由于两者都属于复数操作/结果,并且复数不是数轴(从-inf至+inf),而是二维平面(也包括-inf j和+inf j)。与√x=1的解有+1和-1的情况相似,即数轴上的两个“方向”。由于复数代表一个平面,开方结果在复平面上是一个圆。在这个圆上选择一个结果是数值上不稳定的,因此一些算法会产生不准确的结果。 - MisterMiyagi
@Mister Huh,很酷!这就引出了一个问题,为什么cmath使用不同的算法? - wjandrea
等等,如果你再把结果平方一次,就无法得到原始输入的数字了。这是否意味着实际问题是某种精度丢失? - wjandrea
12
@wjandrea: "cmath为什么使用不同的算法?" <- 不同于什么?你是在问为什么 cmath.sqrt(z) 不直接使用 z ** 0.5 吗?如果是,那么答案是一般的复数幂是一个更加复杂的算法(需要进行复数对数、缩放,然后对结果进行复数指数),比平方根更容易出现精度丢失,并且 cmath.sqrt(z) 很可能比 z ** 0.5 更快速和准确。我的建议是总是使用显式的 sqrt 函数或方法而不是一般幂运算。 - Mark Dickinson
1
@Mark 谢谢,这很有道理。我尝试了 cmath.exp(cmath.log(x)/2) 并得到了与 x ** .5 相当精确的结果。我已经相应地更新了答案。如果你有什么想补充的,请随时提出。我在想,也许它需要一个总结,比如说,“*x ** .5 通常可行,但对于更具体的情况,使用你正在使用的库的 .sqrt() 函数以获得最佳结果和性能。”* - wjandrea

24

SymPy

根据您的目标,尽可能延迟计算平方根可能是个不错的主意。 SymPy 也许可以帮助您。

SymPy 是一个用于符号数学的 Python 库。

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

相比之下,浮点数和十进制数返回的数字非常接近2但不等于2:

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

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

Sympy也能理解更复杂的例子,比如高斯积分

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

15

NumPy

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

文档

负数

对于负实数,它将返回nan,因此可以使用np.emath.sqrt()来处理这种情况。

>>> a = np.array([4, -1, np.inf])
>>> np.sqrt(a)
<stdin>:1: RuntimeWarning: invalid value encountered in sqrt
array([ 2., nan, inf])
>>> np.emath.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])

4
Python的fractions模块及其类Fraction可以进行有理数算术运算。由于大多数平方根是无理数,Fraction类不实现平方根操作。但是,它可以用来近似计算具有任意精度的平方根,因为Fraction的分子和分母是任意精度整数。
下面的方法接受一个正数x和迭代次数,并返回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.,“Verified Real Number Calculations: A Library for Interval Arithmetic”,arXiv:0708.3721 [cs.MS],2007年。

或者,使用Python的math.isqrt,我们可以计算任意精度的平方根:

  • 整数i的平方根,正确值的误差不超过1/2nFraction(math.isqrt(i * 2**(n*2)), 2**n)
  • 整数i的平方根,正确值的误差不超过1/10nFraction(math.isqrt(i * 10**(n*2)), 10**n)
  • 1/2n的倍数x的平方根,正确值的误差不超过1/2nFraction(math.isqrt(x * 2**(n)), 2**n)
  • 1/10n的倍数x的平方根,正确值的误差不超过1/10nFraction(math.isqrt(x * 10**(n)), 10**n)
在上述情况下,ix 必须为0或更大。

这与我的方法类似,只是终止方式不同。我真的很喜欢这个。此外,分数可以替换为其他类似类型,例如固定精度或其他类型。 - Uncle Dino

4

牛顿迭代法

计算平方根最简单和精确的方法是使用牛顿迭代法。

假设你有一个要计算其平方根的数字(num),并且你已经猜测了它的平方根(estimate)。猜测可以是任何大于0的数字,但是选择一个合理的数字可以显著缩短递归调用深度。

new_estimate = (estimate + num/estimate) / 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

最后的结果是对数字平方根最准确的计算。它与内置函数math.sqrt()具有相同的值。

这个回答最初是由gunesevitan发布的,但现在已被删除。


9
我总是惊叹于这种方法的简单性和快速收敛。顺便说一下,在牛顿之前这种方法就已经被知道了(参考链接:https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)。 - Eric Duminil
5
然而,对于求平方根的牛顿迭代法的Python实现,其速度可能永远比内置的sqrt函数慢得多,因此不建议在实际应用中使用。如果回答能讨论何时应该使用这种实现(例如在需要更高的精度或对于任意根更快或展示math.sqrt函数可能做的事情时),则会更好。 - Trilarion
5
math.sqrt 可能直接调用处理器的平方根指令。唯一我能想到使用其他方法的好理由是当你有独特的要求时,例如寻找整数平方根。 - Mark Ransom
在这个版本中,您也可以交换类型,并且使用分数或定点数可以修复其速度较慢的math.sqrt问题。 - Uncle Dino
我认为在Python本身实现如此关键的功能并不是一个好主意。我更愿意将其制作成C函数,然后从Python中调用它。 - Shambhav Gautam
@Shambhav 我不认为有人会将这个解决方案用于关键应用程序。我认为它最多只是一个有用的学习工具。 - wjandrea

3

任意精度平方根

这个算法使用字符串操作将表示十进制浮点数的字符串转换为整数,然后调用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_sqrt 的速度与 Decimal.sqrt 大致相同。但是在128位时,str_sqrtDecimal.sqrt 快2.2倍,在512位时,它比后者快4.3倍,在8192位时,它比后者快7.4倍。
这里有一个在 SageMathCell 服务器上运行的在线版本

还可参见https://dev59.com/3WUp5IYBdhLWcg3wRV9D#70841843。 - PM 2Ring

3

二分搜索

注意:此方法适用于更专业的用例,不一定适用于所有情况。

优点:

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

我个人为一个加密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指出的那样,**这个示例代码无法进行计算**。这是它没有将任何东西转换为浮点数的副作用,因此没有丢失精度。如果根是一个整数,你就会得到它。如果不是,你会得到最大的数字,它的平方比你的数字小。我更新了代码,让它也返回一个布尔值,表示该值是否正确,并修复了一个导致它无限循环的问题(也被@wjandrea指出)。这种通用方法的实现对于较小的数字仍然有些奇怪,但是对于10以上的数字我没有遇到任何问题。

克服这种方法/实现的问题和限制:

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

如果你仍然想使用这种方法并获得浮点结果,将其转换为使用浮点值应该是微不足道的。注意,这将重新引入精度损失,而这种方法对其他方法的独特优势在于此,如果是这种情况,你也可以使用任何其他答案。我认为牛顿法的版本收敛速度更快,但我不确定。

对于更大的数字,浮点数丢失精度的影响将发挥作用,该方法可以给出更接近实际答案的结果(取决于输入的大小)。如果你想在这个范围内使用非整数,你也可以在这个方法中使用其他类型,例如固定精度数字。

编辑2,关于其他答案:

目前,据我所知,唯一另一个具有类似或更好精度的大数解决方案的答案是Eric Duminil建议的SymPy。那个版本也更容易使用,并适用于任何类型的数字,唯一的缺点是它需要SymPy。如果这是你要寻找的东西,我的实现不需要任何巨大的依赖项。


2
如果 d 不是一个完全平方数,那么结果总是小于它的实际根,对吧?这个问题已经有了现成的解决方案:Python 中的整数平方根。你可能也想把这个解决方案发布在那里。还有一个问题:如何找到整数的第n个根? - wjandrea
3
上述函数似乎回答了一个问题:“最大的整数是多少,它的平方能放入d中?” 这类似于 n // p 和“p可以在d中放多少次”这样的问题。 - Eric Duminil
1
@wjandrea 是的,这个实现可以做到。一般的想法也可以用浮点数来实现任意精度(受浮点数精度限制),但是其他答案的方法在大多数方面都更好。 - Uncle Dino
2
编辑后,对于 d=4 -> (1, False) 不起作用。为什么要使用这个实现而不是 math.isqrt() 呢?它总是返回正确的结果并且是在 CPython 中实现的。无论如何,这仍然不是问题所寻找的,即一个实数结果。 - wjandrea
1
是的,牛顿法通常更快。二分法每次循环增加1位结果的精度。牛顿法从一个合理的第一近似值开始(大致上)每次循环将精度翻倍。如果第一近似值不好,牛顿法会退化为二分法,直到值足够接近根。Python浮点数具有53位精度。 - PM 2Ring
显示剩余5条评论

1
一个“正确舍入”的任意精度平方根,适用于任何整数、浮点数或分数,无需循环或转换为十进制数。
(大部分内容来自我的原始回答这里
注意:需要Python 3.8+,因为我在内部使用了math.isqrt来实现这个魔法。
def sqrt(x: Union[int, float, Fraction], precision: int = 53) -> Fraction:
    a, b = x.as_integer_ratio()
    d = a.bit_length() - b.bit_length()
    s = max(precision - 1 - (d-(b<<(d>0 and d)>a<<(d<0 and -d))>>1), 0)
    a <<= s << 1
    n0 = math.isqrt(a // b)
    n1 = n0 + 1
    return Fraction(n1 if n0 * n1 * b < a else n0, 1 << s)

更准确地说,以下内容是有保证的:
  • |√(x) - sqrt(x, precision=p)| < 0.5ulpₚ(√(x))
  • sqrt(float(x), precision=53) == math.sqrt(x) 只要 float(x) <= 2**106(在此之后,math.sqrt 将不再精确)。
  • sqrt(x * x) == x 如果 x 是整数,避免了 this problem
  • 更一般地,sqrt(x, precision=p) 将正确舍入到精度 max(p, ⌊√(x)⌋.bit_length())
  • 对于数学上有兴趣的人: d-(b<<(d>0 and d)>a<<(d<0 and -d))>>1 等于 ⌊log₂(√(a/b))⌋,其中 a > 0。
如果你需要结果以float的形式而不是Fraction,只需执行float(sqrt(x))(尽管这可能会丢失精度或溢出,如果最终结果对于float来说太大)。 注意:如果你只操作整数,有一个稍微简单的函数,对于任何分母为1的整数或Fraction都等效于上述函数。
def sqrt_of_int(x: int, precision: int = 53) -> Fraction:
    s = max(precision - (x.bit_length() + 1 >> 1), 0)
    x <<= s << 1
    n0 = math.isqrt(x)
    n1 = n0 + 1
    return Fraction(n1 if n0 * n1 < x else n0, 1 << s)

一个替代实现原始函数的方法可以如下所示:
def sqrt(x: Union[int, float, Fraction], precision: int = 53) -> Fraction:
    a, b = x.as_integer_ratio()
    precision += 3
    return sqrt_of_int(a, precision) / sqrt_of_int(b, precision)

这个版本的优点是sqrt(a/b)*sqrt(b/a) == 1,而原始版本并不能保证这一点。另一方面,我不确定舍入保证是否还有效。但是,在增加3位精度后,误差界限仍然是保证的。

-5

找到一个数的平方根

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

输出:-

输入一个数字:24
24的平方根 ==> 4.898979485566356
输入一个数字:36
36的平方根 ==> 6
输入一个数字:49
49的平方根 ==> 7

✔ 点击下方查看输出结果 ✔

Output1

Output2


1
为什么在查找整数解时要进行线性搜索,而不是简单地使用math.isqrt(num) ** 2 == num呢?甚至更简单的是,kd = math.sqrt(num); kd.is_integer()? - wjandrea
我尝试使用for循环和质数逻辑来查看输出。 - Shubham-Misal
那个输出结果与那段代码不匹配。请尝试一下。 - wjandrea
@wjandrea 它匹配了输出... :) - Shubham-Misal
请查看图片,因为如果对齐出现错误,则会经常出现错误... :)✔ - Shubham-Misal
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接