☰
MATLAB三角函数运算全解:从弧度角度到向量化实战
2026/10/7 5:00:39 网站建设 项目流程

翻到这个速成笔记系列的第七十三篇,聊一个几乎每个MATLAB新手都觉得自己会、但一出手就翻车的主题:三角函数运算。你回想一下第一次敲下sin(pi)却没得到0的那一刻,是不是整个人愣住了?这个经历比报错还让人印象深刻。很多朋友刚装好MATLAB,第一件事就是拿三角函数试手,结果却跟预期对不上,于是开始怀疑是不是自己数学没学好——其实问题不在你,而在MATLAB对角度单位、数值精度、返回值范围都有自己的约定,不了解这些约定,sin、cos、tan这些看起来人畜无害的函数分分钟给你挖坑。

这篇笔记不打算把三角函数当数学课来讲,而是从实用角度出发,把MATLAB里三角函数运算的底层逻辑、常用函数体系、向量化操作方式、实战场景、性能优化和常见坑位一次理清楚。内容都是我在平时处理信号、做仿真、写图像处理脚本时实际用过的经验,代码可以直接抄走,适合正在学MATLAB的初学者,也适合那些用了一段时间但老在角度制和弧度制之间来回踩坑的进阶用户。读完你至少能弄明白三件事:三角函数到底有哪些函数可用、为什么sind(30)比sin(deg2rad(30))更靠谱、以及怎么在高性能代码里把三角函数用得又快又准。

1. MATLAB三角函数全家桶:先搞清楚你手里有哪些函数

1.1 基础函数体系不止sin/cos

很多人的MATLAB三角函数知识停留在sin、cos、tan这三个上面,其实MATLAB的三角函数家族远比你想的庞大。完整的一套包括正弦、余弦、正切、余切、正割、余割六大基本函数,反三角函数六个,双曲函数六个,反双曲函数六个,再加上一组角度制专用版本,全部算下来超过三十个。平时用不到这么多,但知道有这些东西,至少能在需要的时候想起来去查。

% 基本函数 sin(x) cos(x) tan(x) cot(x) sec(x) csc(x) % 反函数 asin(x) acos(x) atan(x) acot(x) asec(x) acsc(x) % 双曲函数 sinh(x) cosh(x) tanh(x) coth(x) sech(x) csch(x) % 四象限反正切 atan2(y, x)

一个容易忽略的点是:cot、sec、csc这些函数虽然在大多数版本里面都有,但在一些老代码里大家习惯用1./tan(x)、1./cos(x)、1./sin(x)来表示。这背后的原因很简单——有些早期版本对这几个函数的支持不完整,而且用倒数形式一眼就能看出数学关系,别人读代码的时候不用额外记忆。我个人的建议是:新代码里直接用cot、sec、csc,它们本身就是内置函数,速度和精度都有保障;如果是接手别人的老代码,看到1./sin这类写法也别急着改,功能是一样的。

1.2 角度制函数sind/cosd是亲儿子

MATLAB在基本三角函数之外,专门提供了一组带d后缀的函数:sind、cosd、tand、cotd、secd、cscd,以及对应的反函数asind、acosd、atand、atan2d。这组函数最大的特点就是输入输出都使用角度制,不用你手动换算弧度。它们的内部实现并不是简单地把角度转成弧度再调sin,而是针对角度制做了专门的精度处理,后面我会详细展开。

sind(30) % 返回 0.5,直接输入度数 cosd(60) % 返回 0.5 atand(1) % 返回 45,输出单位是度 atan2d(1, 1) % 返回 45,四象限反正切的角度制版本

这组函数在日常处理角度测量、机械结构、地理坐标等场景时特别好用。比如你从传感器读到的数据本身是角度值,直接用sind(theta_deg)比先写一行deg2rad(theta_deg)再调sin要干净得多。而且在代码可读性上,sind直接就把单位写在了函数名里,别人看代码的时候不会产生歧义。

1.3 反三角函数与双曲函数:返回值范围决定一切

反三角函数是很多人容易忽略返回值范围的地方,实际上这个范围才是这类函数的灵魂。asin(x)返回[-pi/2, pi/2],acos(x)返回[0, pi],atan(x)返回[-pi/2, pi/2],而atan2(y, x)返回[-pi, pi]。这几个范围不是随便定的,它们取决于函数在数学上的主值定义。一个经典问题是:atan(y/x)和atan2(y, x)到底有什么区别?atan(y/x)只根据比值算角度,当 x 为负数时会算错象限;atan2(y, x)根据 x 和 y 的符号综合判断象限,返回的才是真正的四象限角。

双曲函数sinh、cosh、tanh在信号处理和微分方程里出场率很高,比如求解带边界条件的波动方程、双曲坐标变换,以及某些非线性激活函数。反双曲函数asinh、acosh、atanh则经常出现在积分化简和特殊函数计算中。我实际遇到最多的是atanh用于处理sqrt(x^2 + a^2)这类积分的结果化简。这些函数平时用不到没关系,但刷题或者做理论研究时遇到它们,至少知道MATLAB里已经内置好了,不用自己用log去拼。

2. 弧度制与角度制:90%错误的根源

2.1 sin(90)不等于1背后的数学约定

新手最常踩的坑,就是直接用sin(90)期望得到1,结果得到一个0.893996663600558。这个数字第一次出现的时候,你会觉得MATLAB是不是算错了。其实没错,因为MATLAB所有不带d后缀的数学函数,默认输入单位都是弧度。sin(90)计算的是90弧度对应的正弦值,90弧度约等于5156.6度,相当于转了好几圈,你再看看正弦值,自然不是什么规整的数字。

这个约定来自数学分析的传统:高等数学里所有导数公式、泰勒展开、傅里叶级数,前提条件都是弧度制。d(sin x)/dx = cos x这个公式只有在弧度制下才严格成立,如果用角度制,求导之后还得乘以pi/180,非常麻烦。所以MATLAB作为面向科学计算的工具,默认使用弧度制是合理的选择,坑只在于新人不知道这个约定。

理解这个逻辑之后,你再看sin(pi/2)等于1、cos(pi)等于近似-1,就会觉得一切顺理成章。这里还有个细节值得留意:sin(pi)并不会精确返回0,而是返回1.224646799147353e-16,这个和浮点数表示pi的精度有关,后面我会专门讲怎么处理这类精度问题。

2.2 sind/cosd的精度优势

使用sin(deg2rad(180))计算180度的正弦,结果同样是一个接近0却不等于0的数;但如果直接用sind(180),你会惊喜地发现结果严格等于0。这不是巧合,而是MATLAB对角度制函数做了专门的参数约简处理。对于整数角度值,sind能把角度约简到精确的区间,避开浮点数表示pi时引入的误差。类似的还有cosd(90)严格等于0,而cos(pi/2)返回6.123233995736766e-17。

fprintf('%.17f\n', sin(pi)); % 1.22464679914735321e-16 fprintf('%.17f\n', sind(180)); % 0.00000000000000000 fprintf('%.17f\n', cos(pi/2)); % 6.12323399573676604e-17 fprintf('%.17f\n', cosd(90)); % 0.00000000000000000

这个差异在绝大多数场景下不影响结果,但在某些对边界条件敏感的计算里会给你添乱。比如判断一个角度经过旋转后是否回到原位置,理想情况旋转180度再旋转180度应该严格重合,如果你用弧度制计算,中间就会残留1e-16级别的误差,导致判断条件永远不成立。对这种边界检测场景,我用sind系列函数直接算,结果干净又省心。

2.3 什么时候必须用弧度制

既然sind这么好用,是不是所有地方都该用它?不是。有几类场景你必须用弧度制,强行用角度制反而会出错。第一类是涉及导数、积分、微分方程求解的场景,这些数学操作的全部理论都建立在弧度制上,你用角度制往里套,求导因子会让你推导过程一团糟。第二类是信号处理中的频率计算,sin(2*pi*f*t)这个形式里f*t是周期数,乘以2*pi之后才是相位,这里的角度天然是弧度。

fs = 1000; % 采样率 1000Hz t = (0:9999) / fs; % 10秒时长 f = 50; % 信号频率 50Hz y = sin(2*pi*f*t); % 用弧度制生成正弦波

如果你在这个例子里换成sind(360*f*t),从数学上也能得到一样的波形,因为360*f*t是同样的相位用度表示。但实际运行时sind内部要对非整数参数做额外的约简计算,性能上不如直接用弧度流畅。我建议养成一个习惯:凡是从数据和公式里来的一律用弧度制,凡是从仪表盘、传感器读数和人工交互中来的一律用角度制,中间用deg2rad和rad2deg做显式转换。代码里变量名带上单位后缀,比如theta_deg、theta_rad,就能彻底避免单位混用。

3. 矩阵与向量化操作:别再用for循环逐个算角

3.1 三角函数天然支持数组运算

MATLAB的一个核心设计理念是让向量和矩阵计算尽可能不需要循环。三角函数在这方面支持得非常好,sin、cos以及所有三角函数族都对数组逐元素生效,不需要显式加点和点乘。你只需要把角度向量准备好,一次性调用函数,就能得到整个结果向量。

% 低效写法 n = 10000; y = zeros(1, n); for k = 1:n y(k) = sin(2*pi*k/n); end % 高效写法 k = 1:n; y = sin(2*pi*k/n);

这两种写法在功能上完全等价,但性能差距是数量级的。我用timeit实测过,n等于一万时循环写法大约需要0.001秒左右,向量化写法往往只有它的几十分之一;当n到百万级别时,循环写法的劣势更加明显。原因在于MATLAB的循环解释执行开销大,而向量化写法直接调用底层经过优化的BLAS级运算,所以能用向量解决就不要写循环。

3.2 atan2四象限判断与相位提取

atan2(y, x)是处理相位、角度、坐标转换时最不该被低估的一个函数。它在极坐标转换中的作用无可替代:给定一个点的直角坐标(x, y),怎么求它相对原点的极角?如果只用atan(y/x),当x为负数时,你会得到一个相差180度的错误角度。而atan2(y, x)通过同时观察x和y的符号,能把角度正确映射到[-pi, pi]区间。

x = -1; y = 1; theta1 = atan(y/x); % 返回 -0.7854,实际应该是 135度 theta2 = atan2(y, x); % 返回 2.3562,对应 135度,正确

在MATLAB里,直接在复数上调用angle(z)就等价于atan2(imag(z), real(z))。分析信号频谱、计算滤波器相频响应、处理FFT结果时,我都会用angle或者atan2来提取相位,而不会用atan去猜象限。当初写极坐标图的时候我在这个地方栽过跟头,后来学乖了:凡是不知道象限的场景,一律atan2,绝不用atan。

3.3 复数指数一把梭的奇技淫巧

有时候你需要同时算出某一组角度的正弦值和余弦值,比如图形学里批量计算旋转向量,或者信号处理里同时需要信号的同相分量和正交分量。按常规思路写两行代码:

s = sin(theta); c = cos(theta);

每行都要对输入角度做一次三角函数求值,等于做了两遍。我后来发现一个实用技巧:用复数指数exp(1i*theta)把正弦和余弦一次算出来,因为欧拉公式e^(i*theta) = cos(theta) + i*sin(theta)恰好同时给两者。

N = 1000000; theta = linspace(0, 100*pi, N); tic; s1 = sin(theta); c1 = cos(theta); t1 = toc; tic; e = exp(1i*theta); s2 = imag(e); c2 = real(e); t2 = toc; fprintf('分开算: %.6f s\n', t1); fprintf('复数算: %.6f s\n', t2);

在MATLAB里,exp(1i*theta)的内部实现通常会对正弦余弦做协同计算,实测下来比分开调用两次三角函数要快。得到的s2、c2和直接调sin、cos的结果在双精度下基本一致,误差在浮点允许范围内。这个技巧在需要同时处理大量角度值的场合很香,比如一次性生成I/Q两路信号、批量计算旋转矩阵的多个角度,可以对性能有直观的提升。

4. 实战场景:从信号生成到相位解缠

4.1 正弦信号发生器与参数选择

工程上最常碰到的三角函数应用,就是生成正弦信号。不管你是做音频处理、振动分析、还是通信仿真,第一步都是给定采样率、频率和时长,生成一组正弦波。完整代码看起来非常简单,但每个参数的选择都有讲究。

fs = 44100; % 音频采样率 44.1kHz dur = 2; % 时长 2秒 f0 = 440; % 频率 440Hz,标准A音 t = (0:round(dur*fs)-1) / fs; y = sin(2*pi*f0*t); sound(y, fs); % 播放出来听听

这里t是离散时间点,单位是秒,f0*t得到的是从0到880的周期计数,再乘以2*pi就变成以弧度为单位的相位。需要注意:离散信号里每个采样点对应的是一个瞬时相位,没有“持续一段时间”的概念,所以一定不要为了追求“完整波形”而把时间向量搞错。还有一点,如果需要在波形里加入初相,通常写成sin(2*pi*f0*t + phi),这里phi的单位也必须是弧度,否则相位偏移看起来会乱掉。

4.2 相位提取与unwrap解缠绕

在信号处理中,提取信号的瞬时相位是常事,但经常会遇到相位跳变的问题。atan2返回的相位被限制在[-pi, pi]区间里,如果真实相位超过这个范围,提取结果就会出现180度甚至360度的突变。这个现象也叫相位缠绕,解缠绕的方法就是unwrap。

fs = 1000; t = (0:1999) / fs; f = 10; true_phase = 2*pi*f*t + 0.5*sin(2*pi*t); % 真实相位持续增长 z = exp(1i*true_phase); % 构造复数信号 wrapped = angle(z); % 在 [-pi, pi] 之间折叠 unwrapped = unwrap(wrapped); % 恢复连续相位 figure; plot(t, true_phase, t, unwrapped); legend('真实相位', '解缠后相位');

实际用unwrap的时候有个注意点:它默认沿着数组的第一个非单一维度解缠,而且要求相邻两点间的相位差不超过pi。如果你的信号采样率太低,相邻采样点之间的真实相位差超过了pi,unwrap也会无能为力,这时候要么增加采样率,要么先用一个粗略估计把相位差压小。解缠之后得到的连续相位在很多场合有重要价值,比如激光干涉测距、多普勒频率估计、锁相环分析,相位连续性直接关系到后续差分求频率的准确性。

4.3 旋转矩阵与坐标变换

三角函数在几何变换中的应用同样深入日常。二维旋转矩阵由正弦和余弦组成,把一个点绕原点旋转某个角度,本质就是一次矩阵乘法。MATLAB里写起来非常直观:

theta = deg2rad(45); R = [cos(theta), -sin(theta); sin(theta), cos(theta)]; pt = [1; 0]; rotated = R * pt; % 旋转45度后的坐标

这里用到了矩阵乘法而非逐元素运算,因为旋转矩阵的数学定义就是矩阵乘以列向量。如果把多个角度批量旋转,可以先把所有角度对应的旋转矩阵堆成三维数组,再用pagemtimes一次性算完。MATLAB自带的pol2cart和cart2pol函数内部也是三角函数和atan2的组合,极坐标转直角坐标时直接调用即可。

[theta, rho] = cart2pol(x, y); % 直角转极坐标 [x, y] = pol2cart(theta, rho); % 极坐标转直角

在机器人运动学、轮廓扫描数据处理中,这类转换几乎每天都要用。理解内部的三角关系才能用好它们,比如cart2pol返回的极角范围就是[-pi, pi],需要0到360度结果时自行加2*pi并取模。

5. 精度与性能优化:算得快还要算得准

5.1 大角度输入的精度陷阱

三角函数是周期函数,理论上对任意大的输入都能求值。但实际浮点数运算中,当输入角度特别大时,直接调用三角函数可能产生显著的精度损失。原因在于:要计算sin(x),内部先把x约简到一个基本区间,例如[0, pi/2],但x本身是浮点数,如果x非常大,约简过程中表示x的有限精度会产生误差,结果的正弦值也跟着偏移。

x = 1e16; y1 = sin(x); % 直接计算 y2 = sin(mod(x, 2*pi)); % 手动约简到 [0, 2*pi] fprintf('%.15f\n%.15f\n', y1, y2);

我实际跑下来两行结果会有一定的差异,而这个差异就是直接计算丢掉的部分。遇到超大角度时,稳妥的做法是先对2*pi取模,或者用角度制函数处理整数度数。另外,MATLAB官方文档也对这类情况给出了提示:要尽量避免使用过大的参数。如果你的物理模型里角度值会累加到很大,建议每几步就做一次角度约简,把相位控制在合理范围内。

5.2 向量化与避免重复计算

前面提到向量化能带来数量级的性能提升,这一节再补充几个容易忽略的优化点。首先,循环里反复调用的三角函数,如果输入是同一个角度,一定要把结果提到循环外存好。比如一个迭代算法里每轮都要用到sin(theta),而theta不变,那就把s = sin(theta)放在循环之前。

% 不推荐 for k = 1:1000 a = sin(theta) * data(k); end % 推荐 s = sin(theta); for k = 1:1000 a = s * data(k); end

其次,当你的问题是规则网格上的角度计算,可以考虑预计算查找表。比如你要画一个固定数量的点分布在圆上,那么每个点的角度是固定序列,先把所有cos和sin结果算好,之后只需要索引。时间复杂度从每次计算三角函数变成了一次查表,代价是写起来稍微麻烦一点,但性能优势很明显,尤其是在嵌入式目标或需要实时响应的程序里。

5.3 波形拟合的初值选择

MATLAB的曲线拟合工具箱可以用正弦函数拟合周期数据,但初值选不好很容易得出莫名其妙的结果。很多人直接写成:

ft = fittype('a*sin(2*pi*f*x + phi)'); f = fit(x, y, ft, 'StartPoint', [1, 1, 0]);

这样大概率拟合不稳定或者收敛到错误的局部最优。要给出好的初值,先画图看数据:a的初值用数据幅度的一半,f的初值用目测周期的倒数,或者直接用FFT找主峰频率,phi的初值用0或者数据过零点的偏移量。

span = max(x) - min(x); f_guess = 1 / span; % 假设整个区间刚好一个周期 a_guess = (max(y) - min(y)) / 2; coeff = fit(x, y, ft, 'StartPoint', [a_guess, f_guess, 0]);

我个人的经验是:正弦拟合失败十有八九是频率初值给得不对。FFT粗估频率这一步虽然多写几行代码,但能显著提高拟合成功率。对于多个频率叠加的数据,就更需要先用频谱分离出主分量,再逐一拟合。

6. 常见问题排查速查表

6.1 高频问题清单

在使用三角函数的过程中,有几个问题反复出现,我把它们整理成一个速查表,遇到类似情况直接对照排查即可。

问题表现根本原因解决方案
sin(30)结果不是0.5MATLAB默认弧度制使用sind(30)
sin(pi)不等于0浮点数表示pi有误差用sind(180)或对结果做容差判断
cos(pi/2)不是精确0浮点数舍入误差用cosd(90)或在比较时加阈值
tan(pi/2)得到极大数而不是无穷接近奇点但非准确奇点用cotd(90)或显式判断输入
atan(y/x)得到的角度方向不对未考虑x、y的象限改用atan2(y, x)
对向量用sin报错可能误用矩阵乘法确认输入维度,直接用逐元素函数
角度累加后三角函数结果异常大角度输入精度下降用mod(x, 2*pi)做参数约简
相位曲线在pi处跳变相位缠绕使用unwrap
循环里逐个调sin/cos很慢未利用向量化一次性构造角度向量再调用函数

6.2 补充避坑心得

表格之外再补充几条我实际踩过的坑。第一条,MATLAB的三角函数也支持复数输入,比如sin(1+2i)会返回复数结果,但很多人不知道这一点,在代码里看到复数输入时容易忽略结果的虚实结构。如果你确实要处理复数的三角运算,MATLAB会按照复数域公式计算结果,没问题,但别把复数输入误当成实数处理。

第二条,代码里写角度变量时,最好把单位含义固化在变量名里,比如theta_deg和theta_rad这样一眼能辨。我见过一个项目里有人一会儿用angle存度、一会儿用angle存弧度,最后某个界面上的角度值差了57倍而找了一整天的bug。单位问题看似简单,在三角函数这种对单位敏感的操作上非常致命。

第三条,判断两个角度是否相等时,不要直接用==比较三角函数结果,比如判断cosd(90)和0是否相等,虽然它严格为0,但其他一些边界情况不能保证。稳妥的做法是定义一个小阈值eps,用abs(value - expected) < 1e-12来判断。

最后分享一个我自己的习惯:在项目里写一个统一的角度单位处理工具函数,把不同的三角函数调用包装起来,参数里显式传入单位。

function y = mysin(x, unit) % 统一入口:unit 为 'rad' 或 'deg' if nargin < 2 unit = 'rad'; end switch lower(unit) case 'rad' y = sin(x); case 'deg' y = sind(x); otherwise error('unit 参数必须是 rad 或 deg'); end

这比任何记忆技巧都管用。项目里所有角度计算都走这个入口,想出错都难。

三角函数本身不难,难的是它和各种计算环境约定之间的衔接。我把这个系列里关于三角函数的笔记写成这样,是希望你在看完后,不只是记住几个函数名,而是真正理解弧度制与角度制的选择逻辑、数组与矩阵的使用边界、精度与性能的取舍方法。下次再敲sin的时候,你知道自己是在用弧度的视野看问题,还是在用角度的习惯做判断,这比任何速查表都重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询