
1. 这道题到底在考什么先看懂题面和背后的意图PTA上这道“实验4-2-1 求e的近似值”说难不难说简单吧每次交作业总有人卡在精度上。我见过很多同学代码逻辑看着没问题但一跑就是WAWrong Answer要么答案差0.000001要么直接超时。其实这类题考的不是你背公式而是三个基本功级数求和怎么用程序语言表达、循环的终止条件怎么写才算对、浮点数的坑你到底踩没踩过。题目一般长这样输入一个精度值 eps比如 0.0001用公式e 1 1/1! 1/2! 1/3! ...逐项累加直到某一项的绝对值小于 eps为止然后输出 e。有的版本会要求固定累加前 n 项有的会要求输入 eps 后输出精确到小数点后6位。先说结论这道题的核心不是“求e”而是让你理解级数展开的逼近过程。e 是个无理数你不可能用有限项算出精确值只能在误差可控范围内截断。而“截断到哪一项”就是题目里 eps 的意义。理解了这一点你就明白为什么循环条件不写对答案就一定会错。这道题适合谁适合正在学C语言循环结构、函数章节的初学者也适合准备PTA天梯赛、期末机考的人。它一方面是考试高频题另一方面也是后续学数值计算、泰勒级数展开的基础模型。把这道题吃透后面做 sin(x)、cos(x) 的级数求和、二分法求根这类题思路是共通的。2. 级数求和的底层逻辑为什么 e 可以这样算2.1 泰勒级数是什么一句话讲清楚数学上指数函数 e^x 可以在 x0 附近展开成泰勒级数e^x 1 x x^2/2! x^3/3! ... x^n/n! ...当 x1 时左边就是 e右边变成e 1 1/1! 1/2! 1/3! ... 1/n! ...这里 “!” 是阶乘n! 1×2×3×...×n比如 3! 64! 24。每一项都在快速变小因为分母的阶乘增长速度远超分子的1这里分子始终是1所以级数收敛得很快。生活化类比想象你往一个杯子里倒水第一杯倒满大半后面每杯都少一点越倒越少永远差一点点永远不满但你可以达到任意想要的“接近满”的程度。 eps 就是“你觉得多满算满”的那个标准。2.2 关键推演从公式到代码的两次转化第一次转化把数学求和符号 Σ 翻译成循环。累加器 sum 从 0 开始循环里每次加一项 item这就是一个标准的“累加器模式”。第二次转化阶乘的计算别傻乎乎地每轮都从头乘一遍。如果每次都用内层循环算 n!时间复杂度是 O(n²)n 小的时候无所谓n 大了会浪费算力。更好的做法是利用递推关系假设当前第 i 项是 item_i 1 / i!那么下一项item_{i1} 1 / (i1)! (1 / i!) / (i1) item_i / (i1)也就是说每一项等于前一项除以当前下标。这是一个迭代关系从 item_0 1 出发每轮 item item / i循环变量 i 从 1 往上走就能一路算出所有项完全不用单独写阶乘函数也避免了重复计算。这里我要强调一个初学者常犯的误解有人会先写一个 int 类型的阶乘变量比如 fact fact * i然后再用 1.0/fact。当 n 超过 12 时int 根本装不下 13!13! 6227020800超出 32 位 int 范围结果直接溢出变成负数或垃圾值答案自然错得离谱。正确姿势是让 item 本身是 double 类型用 item item / i 直接迭代全程不出现大整数。2.3 精度模型循环到底什么时候停题目给的 eps 就是误差容限当某一项的绝对值小于 eps说明后面的项对结果的贡献已经小于你容忍的误差可以停了。但注意几个细节要不要加第一项e 的展开式第一项是 1它不小于 eps除非 eps 比 1 还大这不太可能所以它肯定要被加进去。循环体里先加当前项再计算下一项并判断还是先判断再加这两种写法会导致结果差一项。从“结果精度”角度讲差一项项值小于 eps 时对整体影响确实小于 eps但 PTA 判题一般要求严格匹配输出格式所以以题目描述为准。通常题目会说“直到最后一项小于 eps”注意“最后一项”是已经加到 sum 里的那一项还是循环判断的下一项我建议的做法是先算 item如果 item eps 就累加否则 break。这样循环结束条件就是“当前这一项小于 eps 就不加了”刚好对应“把小于eps的最后一项排除在外”。实际很多题目的标准写法是 do-while 循环体里先加 item 再更新下一项这样最后一次加入的项是 eps 的退出时刚算出的下一项 eps。这种写法是最常见的也能过判题。我下面会给出两个版本你自己对比理解。3. 代码实现两个可运行版本逐行讲清楚3.1 版本一do-while 循环常用写法#include stdio.h int main() { double eps, sum 0.0, item 1.0; int i 1; scanf(%lf, eps); do { sum item; // 先加上当前项 item item / i; // 计算下一项item / i i; } while (item eps); // 下一项还足够大就继续 printf(%.6f\n, sum); return 0; }执行过程拆解初始 sum 0item 1对应展开式第一项 1/0!i 1。第一次循环 sum 1item 1/1 1i 变成 2因为 1 eps假设 eps 0.0001继续。第二次 sum 2item 1/2 0.5继续……直到某次 item 1/12! ≈ 2.087e-9 eps循环退出。最终 sum 就包含了所有 eps 的项并额外把小于 eps 的项排除了。这里注意do-while 结构天然适合“至少执行一次”的场景因为 e 的展开式第一项无论如何都要加正好匹配。3.2 版本二while 循环先说条件#include stdio.h int main() { double eps, sum 1.0, item 1.0; int i 1; scanf(%lf, eps); while (item / i eps) { item item / i; sum item; i; } printf(%.6f\n, sum); return 0; }这个版本把 sum 初始化为第一项 1循环里从第二项开始计算。while 条件用的是“下一项” item/i判断它是否还 eps是则累加否则退出。两个版本的差异在于当你退出循环时sum 中是否包含最后那个小于 eps 的项。版本一不包含因为它是先加再更新版本二也不包含因为条件不成立直接退出。其实两个版本最终结果数值上可能会有一点点差异但在 %.6f 输出下通常差别小于 0.000001一般都能过。不过我还是建议你用 do-while 版本因为它的循环语义更贴近公式的“逐项累加”也不容易把初始化搞错。3.3 输出格式的坑%.6f 还是 %fPTA 这类题输出格式经常要求“保留六位小数”。如果你写成 printf(%f\n, sum)默认输出六位小数这通常也能过但万一题目要求 %.6f 而你用 %g就会因为格式不匹配被判 Presentation ErrorPE格式错误。这类错误比 WA 还气人因为结果明明对就是格式不对。我的习惯是一律使用 printf(%.6f\n, sum)。如果你不确定题目要求几位小数看样例输出。样例输出写 2.718281那就是 6 位写 2.71828那就是 5 位。4. 你可能遇到的坑PTA 判题常见问题与排查实录4.1 int 算阶乘导致的溢出或除法截断这是出现频率最高的问题。很多人第一版代码长这样int fact 1; for (int i 1; i n; i) { fact fact * i; sum 1.0 / fact; }当累加到 13! 时int 溢出fact 变成负值sum 越算越小最后输出 2.718... 附近乱跳或者答案完全错误。此外1.0/fact 这种写法里fact 是 int但 1.0 是 doubleC 语言会自动把 fact 转成 double 再除所以除法本身没问题问题全在 fact 溢出。注意只要看到题目里出现阶乘第一反应就该是“别用 int 存阶乘”。用 double 递推 item item / i是最干净的做法。4.2 eps 输入为 0 时怎么办题目输入 eps 可能很小比如 1e-20也可能就是 0 或接近 0。如果 eps 0理论上循环永远停不下来因为每一项都是正的永远不小于 0。有些题目会用特殊样例测这个边界你如果不做处理程序就会死循环PTA 显示运行超时 TLE。应对思路判断 eps 是否 0。如果是正数正常按题目逻辑走如果题目没限制 eps 范围你可以自然运行因为 double 的 item 会下溢变成 00 0仍然成立循环还是会无限跑。为了避免可以给循环加一个上限比如累加到 1000 项就退出或者把条件写成 item eps item 0后者更严谨。while (item eps item 0) { ... }但实际上 PTA 很少用 eps0 这种恶意样例不过作为练习养成处理边界的习惯对你参加天梯赛、期末考试都很有帮助。4.3 输出值永远差最后一位有时候你觉得自己代码逻辑完全正确sum 算到 2.718281828标准差答案 2.718282结果差 0.000001 被 WA。原因可能是循环终止条件里“最后一项”的判定标准对不上题目。例如题目说“累加直到某项的绝对值小于 eps”你的代码却在该项大于等于 eps 时才累加退出时少加了一项累计误差就可能超过输出精度。解决方法把每一轮的 item 值打印出来对比 eps确认哪一项被截断了。如果差的是最后一项把 do-while 改成“先计算再判断是否累加”根据题目描述调整细节。此外还有可能是你的 eps 读入用 %f 而 scanf 需要 %lf导致 eps 读到垃圾值。double 的 scanf 格式必须是 %lfprintf 可以是 %f。这个细节经常让新手百思不解。4.4 常见问题速查表症状可能原因解决办法答案错得离谱甚至出现负数int 计算阶乘溢出改用 double 递推或直接 item item / i运行超时 TLE循环终止条件永远不满足eps0加 item 0 条件或循环上限结果差 0.000001最后一项判定逻辑与题目不符打印中间项调整加项顺序输入 eps 后程序直接崩溃scanf 的 %lf 写成了 %f修正为 double 匹配的 %lf输出格式错误 PE%.6f 写成 %f 或 %g严格对照样例输出格式多次提交但 WA精度不足累加顺序导致误差累积从第一项向后累加不要倒序累加5. 进一步思考这道题还能怎么变着法考你5.1 变体1求 e^x把公式换成 e^x 1 x x^2/2! ...输入 x 和 eps求 e 的近似值。递推关系就从 item item / i 变成 item item * x / i。这时你还要考虑 x 为负数时项的正负交替但绝对值判断逻辑不变。学会这个变体你就彻底掌握了级数递推的通用写法。5.2 变体2固定累加前 n 项如果题目改成“输入 n计算前 n 项之和”那循环就变成一个简单的 forsum 1.0; item 1.0; for (int i 1; i n; i) { item item / i; sum item; }注意这里的项数计算如果要求前 n 项第一项“1”算第0项还是第1项很容易把人绕晕。我的建议是先在草稿纸上列出前几项第0项1第1项1/1!第2项1/2!然后确定你的循环和 sum 初值怎么配合。只要输出的样例过了说明你的项数理解没问题。5.3 变体3用公式 e lim(11/n)^n有的教材或题目会从另一个角度考“求 e 的近似值”就是用重要极限 e lim (1 1/n)^nn 趋向无穷。这个公式收敛速度比泰勒级数慢得多n 要非常大才能达到同等精度。如果在 PTA 碰到这种角度意味着题目可能对循环次数不敏感或者会给你一个较大的 n 作为输入。这种题的意义在于让你体会到不同算法收敛速度的差异顺便复习浮点运算的误差。我实际测试过用 (11/n)^n 算 e 的近似值n 10000 时只能得到 2.71815精确到小数点后3位而用泰勒级数累加到第 20 项左右就达到 2.718281828直接逼近 double 能表示的最高精度。所以如果题目要求 6 位小数泰勒级数显然更合适。5.4 变体4累加顺序对精度的影响最后说一个进阶话题浮点数相加的精度损失。如果你从大数加小数比如先加所有大项再加后面的微小项因为 double 的有效数字有限约 15-16 位十进制当你把很小的项加到一个很大的累加和上时小数部分会被舍入掉。所以应该从最大项往最小项加这和数值分析里的“求和顺序”原则一致。好在我们的累加顺序本来就是从1开始往后加天然是从大到小精度损失最小。这也解释了为什么别为了图方便先把所有项存进数组再倒序求和那样反而会引入不必要的误差。6. 实操总结与代码模板6.1 可直接套用的通用模板根据上面的讨论我给你一个可以应对大多数“级数求值”题目的模板核心就是“累加项递推”#include stdio.h double sum_series(double x, double eps) { double sum 0.0; double item 1.0; // 第0项 int i 1; do { sum item; item item * x / i; // 递推下一项 i; } while (item eps || item -eps); // 绝对值比较 return sum; } int main() { double eps 1e-6; scanf(%lf, eps); printf(%.6f\n, sum_series(1.0, eps)); // x1即e return 0; }注意一下绝对值比较我用了 item eps || item -eps等价于 fabs(item) eps只是不用额外引入 math.h。如果允许用 fabs更直白while (fabs(item) eps) { ... }用哪个看你个人习惯效果一样。6.2 考前自查清单交作业前逐项核对我在带学生刷 PTA 时会让他们提交前过一遍这个清单能省下很多次无效提交[ ] scanf 里 double 用 %lfprintf 里 double 用 %f 或 %.6f[ ] 循环变量有没有可能死循环eps0 时会不会退出[ ] 阶乘有没有用 int 存会不会溢出[ ] 输出格式是不是和样例完全一致包括空格和换行[ ] 初始值对不对sum 从 1 开始还是从 0 开始item 初值是否为 1[ ] 有没有用更简单的办法递推比每次重算阶乘更快且不容易溢出[ ] 手动算前几项验证1 1 0.5 0.166666 ... 大约等于 2.7186.3 关于这道题在 PTA 题库中的位置“实验4-2-1 求e的近似值”属于 PTA 程序设计类实验辅助教学平台的典型题目通常出现在 C 语言课程的基础实验四中。这个实验编号里的“4”一般指循环结构实验“2-1”表示该实验下的第 2 大题第 1 小题。它旁边往往还有求 pi 的近似值、求 sin(x) 近似值等兄弟题。你在刷题时如果把这几个题放在一起对比做会发现它们共用同一套“级数递推”模板只是分子 x 的幂和分母阶乘的组合不同。一道题会了剩下的只是改一行 item 的计算公式。这种题目在天梯赛里虽然不会直接出原题但作为基础知识储备很重要。天梯赛 L2 经常出现需要数学公式转换的题比如多项式求值、矩阵运算、递推关系。你在实验题阶段把“从数学公式到递推代码”的转换练熟了遇到那些题就能条件反射地写出代码不用现场推导这能节省大量比赛时间。7. 我踩过的坑和一点个人体会最后分享两个实际调试中的小教训。第一个是关于输出调试的。我总是建议学生在怀疑精度时别猜直接打印每一步的 item 和 sum 的中间值肉眼找到是哪一项出了问题。比如 eps 0.0001 时第几项之后 item 开始小于 eps打印出来一看你就知道循环退出时机对不对。这比反复提交让 PTA 告诉你 WA 要高效得多。写代码也好、刷题也好学会用 printf 临时调试是新手最值得养成的习惯比什么调试器都直观。第二个是关于数学和工程思维的差别。我们算 e 时理论上 eps 越小结果越精确但实际上 double 能表示的精度有限当 eps 1e-15 时继续减小 eps 已经不会让结果更准了因为累加项小于 double 的机器精度加进 sum 里根本没有效果。这时候循环却在空转白白浪费时间。如果题目用极端小的 eps 测试你的程序你最好给循环加一个最大次数限制比如 1000 次就退出。这种“明知继续算也无意义”的边界判断在实际工程场景里常常能救你一命——无数程序卡死就是因为一个永不满足的精度条件。这道题表面上是 20 行代码的小练习但它把级数、递推、浮点精度、循环设计、格式化输出这几个 C 语言的关键知识点串在了一起。你把它的每个细节都吃透后面写数值计算相关的代码心里会踏实得多。实验 4-2-1 只是一个起点但起点打牢了后面的路就好走多了。