2.3 平面上的角度和三角学
到目前为止,我们已经使用了两把“尺子”(称为轴和轴)来测量平面上的向量。从原点出发的箭头包含了水平和垂直方向上的可测量位移。实际上,与其使用两把尺子,还不如使用一把尺子和一把量角器。以向量(4, 3)为例,我们可以测量出它的长度为5个单位,然后用量角器确定方向,如图2-34所示。
图2-34 使用量角器测量方向
这个向量的长度为5个单位,方向为从轴正半轴逆时针旋转约37°。像原始坐标对一样,可以用一个新的数对(5, 37°)唯一地确定该向量。这种形式的坐标称为极坐标(polar coordinates),和我们到现在为止所使用的笛卡儿坐标(Cartesian coordinates)一样,能很好地描述平面上的点。
有时候,比如做向量加法时,使用笛卡儿坐标更简单;而其他时候,极坐标更实用,特别是进行向量旋转时。在写代码时,因为没有所谓的刻度尺或量角器,所以只能依赖三角函数。
2.3.1 从角度到分量
反过来思考一下:想象我们已经有了一个角度和一个距离,比如116.57°和3。这两者定义了一对极坐标(3, 116.57°),那么,这个向量的笛卡儿坐标是什么呢?
首先,可以将量角器放在轴上并将刻度0对准原点,以确定向量的方向。从轴正半轴逆时针旋转116.57°,并在这个方向上画一条线(见图2-35)。向量(3, 116.57°)就在这条线上的某处。
图2-35 用量角器测量116.57°
然后用一把尺子,在这个方向上测量出一个距离原点3个单位的点。如图2-36所示,一旦找到这个点,就可以测量出向量的分量,得到近似坐标(-1.34, 2.68)。
图2-36 用尺子测量距离原点3个单位的点的坐标
116.57°这个角度并不是随机选择的,从原点开始沿着这个方向移动,每向左走1个单位,就会上升2个单位。大致位于这条线上的向量包括(-1, 2)和(-3, 6),当然还有(-1.34,2.68),这些向量的坐标长度是坐标长度的2倍(见图2-37)。
图2-37 向116.57°所表示的方向移动,每向左移动1个单位,就向上移动2个单位
在116.57°这个方向上,纵横坐标的比值恰好约等于-2。我们不可能总是幸运地得到一个整数的比值,但每个角度都对应一个固定的比值。图2-38展示了另一个角度,200°。它给出的固定比值为0.36,即每-1个水平单位对应-0.36个垂直单位。
图2-38 在不同角度下,每单位水平距离对应多少垂直距离
给定一个角度,该角度上向量的坐标将有一个固定的比值。这个比值叫作角的正切,正切函数写作tan。到目前为止,你已经看到了正切的几个近似值。
在这里,为表示近似相等,用符号≈而不是=。正切函数是一个三角(trigonometric1)函数,因为它可以用来测量三角形。目前我们还没有告诉你如何计算正切,只指出了几个值。不过Python内置了正切函数,很快就会介绍到,请不用担心。
1trigonometric中的trigon指三角形,metric指测量。
正切函数显然与我们最初的问题有关,即为给定角度和距离的向量寻找笛卡儿坐标。但它实际上并不给出坐标,只给出其比值。在这一点上,另两个三角函数很有帮助:正弦(sin)和余弦(cos)。从角度和距离的关系来看,角的正切等于垂直距离除以水平距离(见图2-39)。
图2-39 向量的距离和角度示意图
相比之下,正弦函数和余弦函数给出了向量的垂直距离、水平距离和整体距离之间的关系,其定义如下面的公式所示。
来看一个具体的示例(见图2-40)。对于一个37°的角,上面的点(4, 3)距离原点5个单位。
图2-40 用量角器测量(4, 3)和轴的夹角
在37°角的方向上每移动5个单位,就会垂直移动大约3个单位,所以:
在37°角的方向上每移动5个单位,就会水平移动大约4个单位,所以:
这是将极坐标转换为对应的笛卡儿坐标的一般方法。如果知道一个角(希腊字母theta,常用来表示角)的正弦和余弦,以及在该方向上的距离,则笛卡儿坐标为,如图2-41所示。
图2-41 图解直角三角形中极坐标到笛卡儿坐标的转换
2.3.2 Python中的三角学和弧度
让我们把三角学知识转化为Python代码:实现一个函数,接收一对极坐标(长度值和角度值)并输出一对笛卡儿坐标(分量和分量的长度)。
主要的问题是Python内置的三角函数与我们使用的单位不同。例如,我们期望,但Python给出结果却大不相同。
>>> from math import tan
>>> tan(45)
1.6197751905438615
Python不使用角度,事实上大多数数学家也不使用角度。他们使用弧度(radian)来替代角度,换算系数是:
1弧度 ≈ 57.296°
之所以这样,是因为一个特殊的数π,它的值约为3.141 59。正是它搭建了角度和弧度之间的桥梁。
π弧度 = 180°
2π弧度 = 360°
绕圆半圈的弧度为π,整圈的弧度是2π,分别与半径为1的圆的半周长和周长一致(见图2-42)。
图2-42 半周长的弧度是π,周长的弧度是2π
可以把弧度看作另一种比值:对于一个给定的角度,它的弧度值表明你已经绕过的半径数。正因为这种特性,弧度本身并没有单位。注意到45° = π/4(弧度),所以正确的处理方式应该如下所示。
>>> from math import tan, pi
>>> tan(pi/4)
0.9999999999999999
现在我们可以用Python提供的三角函数实现一个to_cartesian
函数,接收一对极坐标并返回相应的笛卡儿坐标。
from math import sin, cos
def to_cartesian(polar_vector):
length, angle = polar_vector[0], polar_vector[1]
return (length*cos(angle), length*sin(angle))
利用这一点,可以验证沿着37°角的方向移动5个单位可以接近点(4, 3)。
>>> from math import pi
>>> angle = 37*pi/180
>>> to_cartesian((5,angle))
(3.993177550236464, 3.0090751157602416)
现在可以将极坐标转换为笛卡儿坐标了,下面来看看如何将笛卡儿坐标转换为极坐标。
2.3.3 从分量到角度
给定一对笛卡儿坐标,如(-2, 3),可以使用勾股定理计算向量的长度,即。这是我们要找的极坐标对中的第一个坐标。第二个坐标是角度,可以用表示,指出这个向量的方向(见图2-43)。
图2-43 向量(-2, 3)指向的角度
为了得到,这里提供一些已知条件:,,。剩下的就是找到一个满足这些条件的值。你可以暂停一下,试试估算这个角度值。
理想情况下,我们希望有一种更有效的方法。如果有一个函数可以接收的值并返回,那就太好了。说起来容易做起来难,但Python的math.asin
函数帮助我们实现了这一点。这是一个名为反正弦(asin)的反三角函数实现,能返回符合要求的值。
>>> from math import asin
>>> sin(1)
0.8414709848078965
>>> asin(0.8414709848078965)
1.0
到目前为止,没有什么问题。但角的正弦呢?
>>> from math import sqrt
>>> asin(3/sqrt(13))
0.9827937232473292
这个弧度对应的角度大概是56.3°,如图2-44所示,这个方向是错误的!
图2-44 Python的math.asin
函数看起来返回了错误的角度
math.asin
给出的答案并没有错,另一个点(2, 3)确实位于这个方向。它距离原点的长度是,所以这个角的正弦值也是。这就是为什么math.asin
并不完美,因为不同角度可以有相同的正弦。
反余弦(acos)在Python中被实现为math.acos
,可以用来求出正确的值。
>>> from math import acos
>>> acos(-2/sqrt(13))
2.1587989303424644
这个弧度对应的角度大约为123.7°,可以使用量角器确认是正确的。但这只是偶然,因为还有其他的角度可以给出相同的余弦。例如,(-2, -3)离原点距离也为,所以它所在的角度与的余弦同为。为了找到我们真正想要的的值,必须确保它的正弦和余弦与我们的期望值一致。Python返回的弧度约为2.159,满足这个要求。
>>> cos(2.1587989303424644)
-0.5547001962252293
>>> -2/sqrt(13)
-0.5547001962252291
>>> sin(2.1587989303424644)
0.8320502943378435
>>> 3/sqrt(13)
0.8320502943378437
反正弦、反余弦或反正切函数都不足以找到平面内某个点与轴正半轴的夹角。你在上中学的时候肯定学过如何找到正确的点,这里按下不表,直接切入正题——Python可以帮你完成这个工作。math.atan2
函数接收平面上一个点的笛卡儿坐标(按相反的顺序)作为参数,返回对应的弧度。例如:
>>> from math import atan2
>>> atan2(3,-2)
2.158798930342464
抱歉之前卖了个关子,但这样做是为了让你了解使用反三角函数的潜在陷阱。总而言之,三角函数是很难反解的。多个不同的输入可以产生相同的输出,所以一个输出并不能对应唯一的输入。让我们完成一开始要写的函数:一个从笛卡儿坐标到极坐标的转换器。
def to_polar(vector):
x, y = vector[0], vector[1]
angle = atan2(y,x)
return (length(vector), angle)
可以通过一些简单的示例来验证。to_polar((1,0))
应该是轴正半轴上的1个单位,角度为0。事实上,该函数返回的弧度为0,长度为1。
>>> to_polar((1,0))
(1.0, 0.0)
(这里的输入和输出相同是巧合,它们的几何意义不同。)同样,我们也可以得到(-2, 3)的答案。
>>> to_polar((-2,3))
(3.605551275463989, 2.158798930342464)
2.3.4 练习
练习2.27:确认笛卡儿坐标(-1.34, 2.68)对应的向量的长度约为3。
解:
>>> length((-1.34,2.68)) 2.9963310898497184
十分近似了!
练习2.28:图2-45中是一条从正半轴开始按逆时针方向旋转22°角的直线。根据图2-45,的近似值是多少?
图 2-45
解:直线经过点(10, 4)附近,所以4/10 = 0.4是的合理近似值,如图2-46所示。
图 2-46
练习2.29:转换问题的角度,假设我们知道了一个向量的长度和方向,想找到它的分量该如何做呢?一个长度为15的向量指向37°角,其分量和分量是多少?
解:37°的正弦值大约是3/5,表示沿这个角度每移动5个单位,就会垂直向上移动3个单位。所以,长度为15的向量的垂直分量为3/5·15,即9。
37°的余弦约等于4/5,表示在这个方向上每移动5个单位,就会水平向右移动4个单位,所以水平分量是4/5·15,即12。综上所述,极坐标(15, 37°)与笛卡儿坐标(12, 9)大致对应。
练习2.30:假设从原点出发,沿着从轴正半轴逆时针旋转125°的方向移动8.5个单位,那么最终坐标是什么?已知、,请画图来表示走过的角度和路径。
解:
图2-47显示了最终坐标为 (-4.879, 6.962)。
图 2-47
练习2.31:0°、90°和180°的正弦和余弦各是多少?换句话说,在这些方向上,每单位距离经过多少个垂直和水平单位?
解:对于0°,没有垂直距离,所以;而每移动1个单位的距离就经过轴正半轴方向上的1个单位,所以。
对于90°(逆时针转1/4圈),每移动1个单位的距离就经过轴正半轴方向上的1个单位,所以,而。
最后,对于180°,每移动1个单位的距离都经过轴负半轴方向上的1个单位,所以,而。
练习2.32:图2-48对于一个直角三角形给出了一些精确的测量数据。首先,确认这些长度在直角三角形中的有效性,因为它们必须满足勾股定理。然后,用图中的数据计算、和的值,精确到小数点后三位。
图 2-48
解:代入公式,证实这些边长确实满足勾股定理。
根据正弦、余弦和正切的定义,由边长的比例得出近似的三角函数值。
练习2.33:从另一个角度观察上一个练习中的三角形,用它计算、和的值,精确到小数点后三位。
解:旋转并镜像上一个练习中的三角形,这对它的边长和角度没有影响(见图2-49)。
图2-49 变换上一个练习中的三角形后得到的三角形
调换水平和垂直分量后重新计算,通过边长的比值得出60°角对应的三角函数值。
练习2.34:已知50°的余弦值是0.643。的值是多少,的值又是多少?通过画图来计算。
解:已知50°的余弦值是0.643,可以画出如图2-50所示的三角形。
图 2-50
也就是说,已知两个边长的比值:0.643/1 = 0.643。要找到未知边长,可以使用勾股定理。
在已知边长的情况下,,则。
练习2.35:116.57°对应的弧度是多少?用Python计算这个角的正切值,并确认它约等于-2。
解:116.57°·(1弧度/57.296°) ≈ 2.035弧度。
>>> from math import tan >>> tan(2.035) -1.9972227673316139
练习2.36:cos(10π/6)和sin(10π/6)的值为正还是为负?使用Python计算它们的值并确认。
解:一个完整的圆的弧度是2π,所以π/6是一个圆的1/12。可以想象成把一张比萨切成12块,从正半轴开始逆时针数,角10π/6表示只差两块就转完了。这说明它指向右下方,所以余弦应该是正值,而正弦应该是负值,因为这个方向的水平分量和垂直分量分别是正的和负的。
>>> from math import pi, cos, sin >>> sin(10*pi/6) -0.8660254037844386 >>> cos(10*pi/6) 0.5000000000000001
练习2.37:用下面的列表推导式创建1000个极坐标对应的点。
[(cos(5*x*pi/500.0), 2*pi*x/1000.0) for x in range(0,1000)]
在Python代码中,将这些点转换为笛卡儿坐标,并用线段依次将其连接起来,从而画出一幅画。
解:代码如下所示。
polar_coords = [(cos(x*pi/100.0), 2*pi*x/1000.0) for x in range(0,1000)] vectors = [to_cartesian(p) for p in polar_coords] draw(Polygon(*vectors, color=green))
结果是一朵五瓣的花,如图2-51所示。
图2-51 将1000个点连接而成的图是一朵花
练习2.38:通过“猜测检查法”(guess-and-check)找出(-2, 3)对应的弧度(见图2-52)。
图2-52 点(-2, 3)对应的弧度是多少
提示:显然答案在π/2和π之间。在这个区间内,正切的绝对值总是随着弧度的增大而减小。
解:这是一个在π/2和π之间进行猜测和检查的示例,找一个正切值接近-3/2 = -1.5的角。
>>> from math import tan, pi >>> pi, pi/2 (3.141592653589793, 1.5707963267948966) >>> tan(1.8) -4.286261674628062 >>> tan(2.5) -0.7470222972386603 >>> tan(2.2) -1.3738230567687946 >>> tan(2.1) -1.7098465429045073 >>> tan(2.15) -1.5289797578045665 >>> tan(2.16) -1.496103541616277 >>> tan(2.155) -1.5124173422757465 >>> tan(2.156) -1.5091348993879299 >>> tan(2.157) -1.5058623488727219 >>> tan(2.158) -1.5025996395625054 >>> tan(2.159) -1.4993467206361923
结果肯定在2.158和2.159之间。
练习2.39:在平面上找到另一个与有相同正切值(即-3/2)的点。使用Python的反正切函数
math.atan
来求这个点的弧度值。解:另一个正切值为-3/2的点是(3, -2)。Python的
math.atan
函数返回了这个点对应的弧度。>>> from math import atan >>> atan(-3/2) -0.982793723247329
也就是顺时针方向转动不到1/4圈。
练习2.40:不使用Python,算出笛卡儿坐标(1, 1)和(1, -1)对应的极坐标。找到答案之后,使用
to_polar
来检查一下。解:极坐标中,(1, 1)变成了,(1, -1)变成了。
两个向量之间的夹角是它们与轴所成角度的和或差。在下一个小项目中,会提高一些难度。
练习2.41(小项目):如图2-53所示,恐龙嘴巴的夹角是多少?脚趾的夹角是多少?尾巴的夹角是多少?
图2-53 恐龙图形中一些可以测量或计算的夹角