直接用标准库sin()更优,因其基于CORDIC或minimax多项式,精度高、速度快;自行泰勒展开需角度归约至[-π/2,π/2]并递推计算,否则收敛慢、数值不稳定。

为什么直接用 sin() 就行,还要自己写泰勒展开?
除非你在嵌入式环境没链接标准库、或需要控制精度/性能权衡、或教学验证原理,否则真没必要重写 sin()。标准库的 sin() 是高度优化的(常基于 CORDIC 或 minimax 多项式),比朴素泰勒展开快得多、精度也高得多。自己实现容易掉进“收敛慢”“数值不稳定”“范围不处理”的坑里。
泰勒展开公式和收敛范围怎么选?
正弦函数在 0 处的泰勒级数是:sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ...
这个级数对所有实数 x 都收敛,但**收敛速度随 |x| 增大急剧变慢**。比如 x = π(≈3.14)时,前 5 项误差还约 0.001;x = 10 时,要算到 20 项以上才勉强到 1e-6 精度——这既慢又易因浮点累加误差失准。
- 必须先做**角度归约**:利用
sin(x) = sin(x mod 2π),再进一步映射到 [-π/2, π/2] 或 [0, π/2] 区间(例如用sin(π - x) = sin(x)),让输入绝对值 ≤ π/2(≈1.57) - 归约后,|x| ≤ 1.57,通常 7~9 项就能达到
double有效位(约 15 位十进制) - 别硬算阶乘:每项递推计算,比如第 n 项(奇数幂)可由上一项乘
-x*x / ((2k)*(2k+1))
怎么写一个靠谱的 C++ 实现?
关键不是“能跑”,而是“不崩、不错、不慢”。下面是一个最小可行版本,含归约和递推:
double my_sin(double x) {
// 归约到 [-π, π]
x = fmod(x, 2 * M_PI);
if (x > M_PI) x -= 2 * M_PI;
else if (x < -M_PI) x += 2 * M_PI;
<pre class='brush:php;toolbar:false;'>// 利用对称性映射到 [0, π/2]
bool neg = false;
if (x < 0) { x = -x; neg = true; }
if (x > M_PI_2) x = M_PI - x;
// 泰勒展开:sin(x) ≈ x - x³/3! + x⁵/5! - ...
double term = x, sum = term;
double x2 = x * x;
for (int k = 1; k <= 8; ++k) {
term *= -x2 / ((2*k) * (2*k + 1));
sum += term;
}
return neg ? -sum : sum;}
立即学习“C++免费学习笔记(深入)”;
注意:M_PI 和 M_PI_2 需定义 #define _USE_MATH_DEFINES 并包含 <cmath>;fmod 比 % 支持浮点;循环上限 8 是经验值,对应到 x⁹/9! 项,在 |x|≤1.57 下足够。
最容易被忽略的三个坑
实际调试时,90% 的问题出在这儿:
-
fmod对负数行为:C++ 中fmod(-3.5, 2.0)返回 -1.5,不是 0.5,所以归约逻辑必须显式处理符号 - 阶乘爆炸导致中间项溢出:不用递推而直接算
pow(x, 2*k+1)/factorial(2*k+1),factorial(21)就超过long long范围,double阶乘也会迅速失准 - 小量相减放大误差:当
x很小时(如 1e-8),前几项几乎抵消,靠后面高阶项贡献结果,此时浮点舍入误差会显著影响最后几位有效数字——这不是算法问题,是浮点本质限制
归约不准、递推写错、忽略浮点误差边界,这三个点卡住的人最多。写完务必用 my_sin(0.1)、my_sin(M_PI/2)、my_sin(100.0) 对比 std::sin() 验证。


















