>>> 3 - 9
-6
>>> 6 + 1
7
整数加法和减法,等同于小学算术的内容。
但是,要注意,以下关于浮点数的计算。
>>> 3.14 + 2.1
5.24
>>> 2.3 - 4.1
-1.7999999999999998
>>> 4.1 - 2.3
1.7999999999999998
在2.3 - 4.1
的计算中,所得的结果并不是-1.8
——显然应该是这个。这是因为,在利用Python进行计算的时候,要将上面示例中所使用的十进制数字,转换为二进制的数字。在这个转换中,并不是所有的浮点数都对应有限个数的二进制数字,如果没有有限个数的二进制数字,那么计算的时候就产生了误差。这并不是Python的Bug。
要解决这个问题,可以使用四舍五入的方法。
>>> a = 2.3 - 4.1
>>> round(a,1)
-1.8
四舍五入,已经能够解决绝大部分问题了。
这里使用了Python的内置函数round()
,用它实现四舍五入计算。可以用help(round)
的方式查看它的使用方法。
round(number, ndigits=None)
Round a number to a given precision in decimal digits.
The return value is an integer if ndigits is omitted or None. Otherwise
the return value has the same type as the number. ndigits may be negative.
参数ndigits
表示要保留的有理数位数。
如果参数number
是浮点数,当ndigits
是正数的时候,就表示所保留的小数位数。例如上面的示例。
看上面文档,ndigits
还可以是负数,其含义为有理数的整数部分所不保留的位数。例如:
>>> round(163, -2)
200
>>> round(126.78, -1)
130.0
乘法使用一个 *
实现。
>>> 2 * 3
6
>>> 2.3 * 4.5
10.35
乘方运算,可以使用 **
实现:
>>> 2 ** 3
8
>>> 2.3 ** 2
5.289999999999999
根据数学中的知识,指数还可以不是整数,例如
>>> 4 ** (1/2)
2.0
但注意,此时返回的是浮点数。
当然,指数变成小数,与分数是同样的含义。
>>> 4 ** 0.5
2.0
指数也可以是负数,如
>>> 2 ** -2
0.25
除了使用 **
实现乘方运算,还可以用内置函数实现。
>>> pow(2, 3)
8
>>> pow(4, -2)
0.0625
>>> pow(4, 1/2)
2.0
pow()
的第一个参数是底,第二个是幂。用上面的方法,与使用 **
完全一致,即pow(n, m)
等于 n ** m
。
如果查看pow()
的帮助文档:help(pow)
:
pow(base, exp, mod=None)
Equivalent to base**exp with 2 arguments or base**exp % mod with 3 arguments
pow()
函数有三个参数,如果要给mod
参数赋值,则表示base ** exp % mod
,其含义是base
的exp
次方,然后除以mod
,返回其余数。例如:
>>> pow(2, 3, 5)
3
%
,见后面的除法计算。
在数学中,对于很大或者很小的数,习惯使用科学计数法,例如
>>> 2e2
200.0
>>> 2e-2
0.02
>>> e3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'e3' is not defined
>>> 1e3
1000.0
必须要在e
前面有数字,可以是整数,也可以是浮点数。
>>> 23e2
2300.0
>>> 23e-2
0.23
>>> 12.34e2
1234.0
Python3.x与Python2.x对除法的定义有所不同,这里所演示的是Python3.x中的除法。
>>> 3 / 2
1.5
>>> 4 / 2
2.0
>>> 1 / 5
0.2
如果用//
,结果返回商,俗称“地板除”。
>>> 5 // 2
2
>>> 3 // 4
0
>>> 3.4 // 2
1.0
>>> 6.4 // 3
2.0
“地板除”能够得到商,用%
则得到余数。
>>> 5 % 2
1
在内置函数中,有divmod()
,可以同时返回商和余数。
>>> divmod(5, 2)
(2, 1)
Python中表示分数,实用标准库中的一个模块fractions
。
>>> from fractions import Fraction
>>> a = Fraction(3, 4)
>>> b = Fraction(2, 6)
>>> a
Fraction(3, 4)
>>> b
Fraction(1, 3)
其中,a
是分数 b
在定义的时候,是分数
分数之间可以进行运算。
>>> a + b
Fraction(13, 12)
>>> a * b
Fraction(1, 4)
>>> a - b
Fraction(5, 12)
>>> a / b
Fraction(9, 4)
>>> a ** b
0.9085602964160698
>>> pow(a, b)
0.9085602964160698
分数也可以跟整数、浮点数进行运算。
>>> a + 2
Fraction(11, 4)
>>> a * 2.1
1.5750000000000002
>>> b / 4
Fraction(1, 12)
>>> b / 2.5
0.13333333333333333
>>> a ** 2
Fraction(9, 16)
>>> pow(a, 2)
Fraction(9, 16)
前面讨论中,所涉及到的都是实数。Python中也支持定义复数(complex number)。
# 定义1,注意表示虚数必须用j,不能用数学中习惯的i
>>> a = 2 + 3j
>>> type(a)
<class 'complex'>
# 定义2
>>> b = complex(4, 5)
>>> b
(4+5j)
# 只定义虚部
>>> 2j
2j
>>> type(2j)
<class 'complex'>
同时,支持复数的有关基本运算。
>>> a + b
(6+8j)
>>> a - b
(-2-2j)
>>> a * b
(-7+22j)
>>> a / b
(0.5609756097560976+0.0487804878048781j)
>>> a ** b
(-0.7530458367485596-0.9864287886477445j)
通过复数的如下两个方法,分别得到其实部和虚部。
# 实部
>>> a.real
2.0
# 虚部
>>> a.imag
3.0
复数的共轭。数学中定义
>>> a
(2+3j)
>>> a.conjugate()
(2-3j)
在数学中,因为
# 这样做不行,因为这样做会认为 j 是变量
>>> j ** 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'j' is not defined
# 必须这样做
>>> 1j ** 2
(-1+0j)
>>> 2j ** 2
(-4+0j)
对于复数
利用前面已经学过的运算,可以计算:
>>> (a.real ** 2 + a.imag ** 2) ** 0.5
3.605551275463989
最简捷的方法,是使用内置函数abs()
直接计算
>>> abs(a)
3.605551275463989
函数abs()
中的参数也可以是实数,即得到了该实数的绝对值。
>>> abs(-2)
2
Python标准库有一个模块math
,其中包括了常用的初等函数。
>>> import math
>>> dir(math)
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
例如,$$\pi$$ 可以是:
>>> math.pi
3.141592653589793
计算
>>> math.sin(2*math.pi)
-2.4492935982947064e-16
>>> math.sin(math.pi/2)
1.0
有了math
模块,常见函数计算就容易了。
神奇的斐波那契数列。公元1150年印度数学家首先描述了斐波那契数列,此后,意大利人斐波那契(Leonardo Fibonacci)在描述兔子繁殖的数量,这样描述:
- 第一个月初有一对刚诞生的兔子
- 第二个月之后(第三个月初)它们可以生育
- 每月每对可生育的兔子会繁殖一对新兔子
- 兔子永不死
假设在
即设
按照表达式,可知斐波那契数是:$$1,1,2,3,5,8,13,21,34,55,89,144,233,\cdots$$
编程序,计算斐波那契数。可以有多种方法。
递归
这里编写了一个用递归方法计算斐波那契数的函数,关于函数的创建方法,请参阅参考文献[1]。
# 递归
def fib_recu(n):
if n <= 1:
return n
else:
return fib_recu(n-1) + fib_recu(n-2)
print("fibonacci number:")
for i in range(10):
print(f"{fib_recu(i)}", end=" ")
# 输出:
fibonacci number:
0 1 1 2 3 5 8 13 21 34
递归方法,思路简单,但是执行效率比较低。
循环
def fib_loop(n):
fibs = [0, 1]
if n <= 1:
return n
else:
for i in range(n-2):
fibs.append(fibs[-2] + fibs[-1])
return fibs
print(f"fibonacci number: {fib_loop(10)}")
# 输出:
fibonacci number: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
循环方法,也称为递推法,即递增。随着数量的增大,执行速度会越来越慢。
迭代器对象
class Fibs:
def __init__(self, max):
self.max = max
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
fibs = Fibs(100000)
lst = [ fibs.__next__() for i in range(10)]
print(lst)
# 输出
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
生成器对象
def fibs():
prev, curr = 0, 1
while True:
yield prev
prev, curr = curr, prev + curr
import itertools
print(list(itertools.islice(fibs(), 10)))
# 输出
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
通过fibs()
得到了含有无限项斐波那契数的对象,只不过在上面只向内存中读入了10个。
矩阵
import numpy as np
def fib_matrix(n):
F = np.mat([[1,1], [1,0]])
F_m = pow(F, n)
return F_m[0, 0]
print("fibonacci numbers:")
for i in range(10):
print(fib_matrix(i), end=" ")
# 输出
fibonacci numbers:
1 1 2 3 5 8 13 21 34 55
如果用斐波那契数列中的后面的数字除以前面的数字,结果会如何?
def fib_iter():
prev, curr = 0, 1
while True:
yield prev
prev, curr = curr, prev + curr
import itertools
fibs = list(itertools.islice(fib_iter(), 100))
for i in range(2, 30):
print(f"{fibs[i+1]}/{fibs[i]}={fibs[i+1]/fibs[i]}")
# 输出
2/1=2.0
3/2=1.5
5/3=1.6666666666666667
8/5=1.6
13/8=1.625
21/13=1.6153846153846154
34/21=1.619047619047619
55/34=1.6176470588235294
89/55=1.6181818181818182
144/89=1.6179775280898876
233/144=1.6180555555555556
377/233=1.6180257510729614
610/377=1.6180371352785146
987/610=1.618032786885246
1597/987=1.618034447821682
2584/1597=1.6180338134001253
4181/2584=1.618034055727554
6765/4181=1.6180339631667064
10946/6765=1.6180339985218033
17711/10946=1.618033985017358
28657/17711=1.6180339901755971
46368/28657=1.618033988205325
75025/46368=1.618033988957902
121393/75025=1.6180339886704431
196418/121393=1.6180339887802426
317811/196418=1.618033988738303
514229/317811=1.6180339887543225
832040/514229=1.6180339887482036
从上面的输出结果发现:
这就是我们熟悉的黄金分割——黄金比例$$^{[3]}$$,准确值是
一般取
在历史上,伟大的天文学家开普勒$$^{[4]}$$,最早发现了这个结论。
开普勒(Johannes Kepler),德国天文学家、数学家。提出了著名的开普勒三定律,被誉为“为天文立法的人”设
根据斐波那契数列的特点,设定初始条件:$$F_0=0, F_1=1$$ 。
将(5.1)式写成:
以矩阵方式表示为:
推导通项表达式
令 $$\pmb{u}k=\begin{bmatrix}F{k+1}\F_{k}\end{bmatrix}$$ ,由(5.2)式得到:
其中,$$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$ ,初始条件 $$\pmb{u}_0=\begin{bmatrix}1\0\end{bmatrix}$$ 。
(5.3)式即为《机器学习数学基础》第3章3.2.2节中所介绍的差分方程。又因为:
$$\pmb{u}k=\pmb{Au}{k-1}=\pmb{A}(\pmb{Au}{k-2})=\pmb{A}^2\pmb{u}{k-2}=\cdots=\pmb{A}^k\pmb{u}_0\tag{5.4}$$
所以,只要计算出
对于矩阵 $$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$ ,其特征多项式:
根据特征方程:$$|\pmb{A}-\lambda\pmb{I}|=0$$ ,得:
解得:
显然,$$\lambda_1+\lambda_2=1,\lambda_1\lambda_2=-1$$ 。
再根据
当
可得:$$\begin{bmatrix}x_1\y_1\end{bmatrix}=\begin{bmatrix}\lambda_1\1\end{bmatrix}$$ 是满足条件的一个向量,即特征向量 $$\pmb{x}_1=\begin{bmatrix}\lambda_1\1\end{bmatrix}$$ (将 $$\begin{bmatrix}\lambda_1\1\end{bmatrix}$$ 代入到上式,并利用(5.5)式,上式成立,即说明 $$\begin{bmatrix}\lambda_1\1\end{bmatrix}$$ 是一个基向量)。
同理,求得另外一个特征值
很显然,特征向量
设
因为:$$\pmb{A}^k = \pmb{C}\pmb{D}^k\pmb{C}^{-1}$$
于是(5.4)式可以转化为:
由于
若用一般化的式子表示,$$\pmb{C} = \begin{bmatrix}\pmb{x}_1&\cdots\pmb{x}_n\end{bmatrix}$$ (由
即:
若
现在将 $$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$ 的特征向量(5.7)和特征值(5.6)式分别代入(5.10)式,即得到(5.3)差分方程的通解:
当
解得:
代入(5.11),同时将特征值和特征向量也代入,得:
在(5.3)式中假设 $$\pmb{u}k=\begin{bmatrix}F{k+1}\F_{k}\end{bmatrix}$$ ,对比上式,则:
这样就得到了斐波那契数列通项表达式。
推导通项表达式
由于 $$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$ 可对角化,根据
其中 $$\pmb{C}=\begin{bmatrix}\lambda_1&\lambda_2\1&1\end{bmatrix}$$ (
由(5.4)式可得:
将(5.13)代入上式,可得:
所以,通项表达式:
与(5.12)式一样的结果。
性质1:
证明
对于
( $$\because \quad \begin{vmatrix}\frac{1-\sqrt{5}}{1+\sqrt{5}}\end{vmatrix}\lt 1, \quad \lim_{k\to\infty}\left(\frac{1-\sqrt{5}}{1+\sqrt{5}}\right)^k=0$$ )
证毕。
所以,在前面的程序中,显示后项除以前项的结果趋近于黄金分割。
性质2: 幂矩阵
因为 $$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$ ,又因为斐波那契数列
以此类推,可得:
其中 $$\pmb{A}=\begin{bmatrix}1&1\1&0\end{bmatrix}$$
这就是矩阵法计算斐波那契数列的程序编写依据。
**性质3:**Cassini等式
在《机器学习数学基础》第2章2.4节中曾经介绍过行列式的性质,下面应用其中一条:$$|\pmb{AB}|=|\pmb{A}||\pmb{B}|$$ 。
由(5.14)可得:
又因为:
所以:
(5.15)式即为Cassini等式。表明了斐波那契数列的数之间的一种关系。如:
**性质4:**前
由(5.1)式得:
上面各式等号相加,得:
因为
**性质5:**每项的平方和
又因为:$$F_2=F_1=1$$ ,所以:
斐波纳契数是帕斯卡三角形的每一条红色对角线上数字的和。
[1]. Python大学实用教程. 齐伟. 北京:电子工业出版社. 2019年3月,第1版
[2]. 跟老齐学Python:数据分析. 齐伟. 北京:电子工业出版社. 2018年6月,第1版
[3]. https://zh.wikipedia.org/wiki/黄金分割率
[4]. 开普勒的墓志铭:Mensus eram coelos, nunc terrae metior umbras;Mens coelestis erat, corporis umbra iacet.(“我曾测天高,今欲量地深。”“我的灵魂来自上天,凡俗肉体归于此地。”)https://zh.wikipedia.org/wiki/约翰内斯·开普勒