翻到这个速成笔记系列的第七十三篇聊一个几乎每个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。如果你的信号采样率太低相邻采样点之间的真实相位差超过了piunwrap也会无能为力这时候要么增加采样率要么先用一个粗略估计把相位差压小。解缠之后得到的连续相位在很多场合有重要价值比如激光干涉测距、多普勒频率估计、锁相环分析相位连续性直接关系到后续差分求频率的准确性。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(12i)会返回复数结果但很多人不知道这一点在代码里看到复数输入时容易忽略结果的虚实结构。如果你确实要处理复数的三角运算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的时候你知道自己是在用弧度的视野看问题还是在用角度的习惯做判断这比任何速查表都重要。