在Python中计算一个数字的平方根

556 阅读5分钟

简介

一个数字的平方根是一个非常常见的数学函数,用于科学的各个方面--物理学、数学、计算机科学等等。数字和表达式的平方根在所有科学问题的公式中都非常常见,特别是在我们表示现实的方式中--通过微积分对我们可以观察到的东西进行建模。

在这篇文章中,我们将看看在Python中计算一个数字的平方根的各种方法。最后,我们将做一个 性能基准用常数和随机数,以及随机数列表来测试所有的方法。

用NumPy在Python中计算平方根

NumPy是一个科学计算库,它在许多应用和用例中都能找到。当然,它有许多数学函数的包装器作为辅助方法。

如果还没有安装,你可以通过pip

$ pip install numpy

就NumPy而言,sqrt() 函数计算一个数字的平方根,并返回结果。

import numpy as np
x = np.sqrt(2)
print(x)

这就导致了。

1.4142135623730951

除了接受单个变量作为参数外,sqrt() 还能够解析列表并返回一个平方根的列表。

arr = [2, 3, 5, 7]
roots = np.sqrt(arr)
print(roots)

其结果是:。

[1.41421356 1.73205081 2.23606798 2.64575131]

不过,sqrt() 函数有一个限制--它不能计算负数的平方根,因为实数的平方根运算只定义为正数。

试图将-4 插入到sqrt() 函数中会导致一个异常。

print(np.sqrt(-4))

试图计算一个负数的平方根将导致一个警告和一个nan 值。

RuntimeWarning: invalid value encountered in sqrt
nan

用Numpy计算复数的平方根

幸运的是,NumPy并不局限于只处理实数 - 它也可以处理复数。

import numpy as np

complex_number = -1 + 1j
complex_array = [-2, 3, complex_number]

complex_root = np.sqrt(complex_number)
complex_array_roots = np.sqrt(complex_array)

print(f"Square root of '{complex_number}':\n {complex_root}")
print(f"Square roots of '{complex_array}':\n {complex_array_roots}")

如果在一个列表中至少有一个复数,所有的数字都会被转换为复数,所以即使是负整数也可以被添加。

Square root of '(-1+1j)':
 (0.45508986056222733+1.09868411346781j)
Square roots of '[-2, 3, (-1+1j)]':
 [0.        +1.41421356j 1.73205081+0.j         0.45508986+1.09868411j]

Python的数学模块

math 模块是一个与 Python 一起打包的标准模块。它总是可用的,但必须被导入,并为一些常用函数提供封装,如平方根、幂等。

import math
math.sqrt()

math 模块的sqrt() 函数是一个直接的函数,可以返回任何正数的平方根。

print(math.sqrt(2))

这就导致了。

1.4142135623730951

不像NumPy的sqrt() 函数,它只能对单个元素工作,所以如果你想计算一个列表中所有元素的平方根,你必须使用for 循环或列表理解。

import math

arr = [2, 3, 5, 7]
roots = []

for x in arr:
    roots.append(math.sqrt(x))

# OR
roots = [math.sqrt(x) for x in arr]

在这两种情况下,roots 列表将包含。

[1.4142135623730951, 1.7320508075688772, 2.23606797749979, 2.6457513110645907]
math.pow()

一个数字的平方根也可以通过将一个数字提高到½的幂来计算。

sqrtx=xfrac12\\sqrt x = x^{\\frac 1 2}

因此,实际上,寻找一个数字的平方根可以表示为将该数字提高到½的幂。math.pow() ,需要两个参数--基数和指数,并将基数提高到指数的幂。

print(math.pow(2, 0.5))

自然,这就产生了。

1.4142135623730951

**运算符

** 运算符是一个二进制运算符,这意味着它对两个数值起作用,就像常规的乘法运算* 。然而,由于它是一个用于指数化的运算符,我们把它的左参数提高到其右参数的幂。

这种方法的使用形式与前一种方法相同。

print(2 ** 0.5)

而且它的结果也是。

1.4142135623730951

*pow()*函数

Python 有另一个内置的pow() 方法,不需要导入math 模块。这个方法在技术上与math.pow() 方法内部不同。

math.pow() 隐含地将元素转换为双数,而 使用对象的内部实现,基于 操作符。虽然这种实现上的差异可能证明在某些情况下可以使用一个或另一个,但如果你只是计算一个数字的平方根,你不会真正看到这种差异。pow() **

print(pow(2, 0.5))

这就导致了。

1.4142135623730951

性能基准

那么,哪一个能产生最好的性能,你应该选择哪一个?像往常一样,并没有一个明确的赢家,这取决于方法的使用情况。也就是说,如果你正在处理常数、随机数或更大范围内的随机数阵列--这些方法的表现会有所不同。

让我们在常数、随机数和随机数数组上对它们进行测试。

import timeit

print("Time to execute 100k operations on constant number: \n")
print("math.sqrt(): %ss" % timeit.timeit("math.sqrt(100)", setup="import math", number=100000))
print("math.pow(): %ss" % timeit.timeit("math.pow(100, 0.5)", setup="import math", number=100000))
print("pow(): %ss" % timeit.timeit("pow(100, 0.5)", number=100000))
print("np.sqrt(): %ss" % timeit.timeit("np.sqrt(100)", setup="import numpy as np", number=100000))
print("** operator: %ss" % timeit.timeit("100 ** 0.5", number=100000))

print("\nTime to execute 100k operations on random number: \n")
print("math.sqrt() %ss" % timeit.timeit("math.sqrt(random.random())", setup="import math; import random;", number=100000))
print("math.pow(): %ss" % timeit.timeit("math.pow(random.random(), 0.5)", setup="import math; import random", number=100000))
print("pow(): %ss" % timeit.timeit("pow(random.random(), 0.5)", setup="import random", number=100000))
print("np.sqrt(): %ss" % timeit.timeit("np.sqrt(random.random())", setup="import numpy as np; import random", number=100000))
print("** operator: %ss" % timeit.timeit("random.random() ** 0.5", setup="import random", number=100000))

print("\nTime to execute 100k operations on list of random numbers: \n")
print("math.sqrt() %ss" % timeit.timeit("[math.sqrt(x) for x in np.random.rand(100)]", setup="import math; import numpy as np;", number=100000))
print("math.pow(): %ss" % timeit.timeit("[math.pow(x, 0.5) for x in np.random.rand(100)]", setup="import math; import numpy as np;", number=100000))
print("pow(): %ss" % timeit.timeit("[pow(x, 0.5) for x in np.random.rand(100)]", setup="import numpy as np;", number=100000))
print("np.sqrt(): %ss" % timeit.timeit("np.sqrt(np.random.rand(100))", setup="import numpy as np; import numpy as np;", number=100000))
print("** operator: %ss" % timeit.timeit("np.random.rand(100) ** 0.5", setup="import numpy as np", number=100000))


我们把上面列出的所有方法都通过了同样的测试--一个常数(这可能是为了优化而被缓存的),一个100k迭代中的每个随机数,以及一个100个随机数的列表

**注意:**与该测试中的其他方法相比,只有每个测试上的相对数字是相关的,因为生成100个随机数要比使用(缓存的)常数值花费更多时间。

运行这段代码的结果是。

Time to execute 100k operations on constant number: 

math.sqrt(): 0.014326499999999999s
math.pow(): 0.0165132s
pow(): 0.018766599999999994s
np.sqrt(): 0.10575379999999998s
** operator: 0.0006493000000000193s

Time to execute 100k operations on random number: 

math.sqrt() 0.019939999999999958s
math.pow(): 0.022284300000000035s
pow(): 0.0231711s
np.sqrt(): 0.09066460000000004s
** operator: 0.018928s

Time to execute 100k operations on list of random numbers: 

math.sqrt() 2.7786073s
math.pow(): 2.9986906s
pow(): 3.5157339999999992s 
np.sqrt(): 0.2291957s
** operator: 0.2376024000000001s

对于常数--math.pow(),math.sqrt()pow() 函数明显优于NumPy的sqrt() 函数,因为它们可以更好地利用语言层面上CPU中的缓存。

对于随机数,缓存的效果并不理想,我们看到的差异较小。

对于随机数列表,np.sqrt() 的性能明显优于所有三种内置方法,而** 操作符的性能也在同一范围内。

总结一下。

  • 对于常数** 算子显然在测试机器上表现最好,执行速度比内置方法快16倍。
  • 对于随机数np.sqrt() 比内置方法和** 算子要好,不过,结果并没有明显的差异。
  • 对于随机数组np.sqrt() 函数优于内置方法,但** 算子非常接近。

根据你要处理的具体输入--你会在这些函数中选择。虽然看起来它们会有很好的表现,虽然在大多数情况下不会有太大的区别,但在处理巨大的数据集时,即使处理时间减少10%,也会对长远发展有所帮助。

根据你要处理的数据--在你的本地机器上测试不同的方法

总结

在这篇短文中,我们已经看了在Python中计算数字平方根的几种方法。

我们看了math 模块的pow()sqrt() 函数,以及内置的pow() 函数,NumPy 的sqrt() 函数和** 操作符。最后,我们对这些方法进行了基准测试,以比较它们在不同类型的输入--常数、随机数和随机数列表上的性能。