ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PTA求e的近似值:从循环递推到浮点精度的完整解析

PTA求e的近似值:从循环递推到浮点精度的完整解析 题目是PTA平台上很经典的入门循环题“实验4-2-1 求e的近似值”但这道题的水其实很深。很多人觉得不就是泰勒展开求e嘛结果在PTA上反复提交、反复报错有的卡在格式上有的卡在精度上还有的直接连题目都没读懂。我当初做这道题的时候也踩了不少坑今天干脆把这题的里里外外掰开揉碎了聊一遍从数学原理到代码实现、再到PTA判题的那些小脾气一次性说清楚。1. 题目到手先别急着敲代码先看清它到底要什么这道题在PTA上的完整题干一般是这样的利用公式 e 1 1/1! 1/2! 1/3! ... 1/n! 求e的近似值要求最后一项也就是 1/n!小于给定的精度阈值 eps 时停止累加输出e的近似值。有些变体还会要求输出累加了多少项也就是n的值。先说结论这道题的核心考点根本不是“求e”而是循环终止条件的设置和浮点数累加精度。这两个点恰恰是新手最容易翻车的地方。为什么这么说因为求e本身的数学公式是现成的没有任何推导难度。真正写代码的时候难点在于你怎么判断“最后一项小于eps”你累加的时候是老老实实每次重算阶乘还是用递推关系省事你的循环变量类型是 int 还是 double你输出格式到底该用%.6f还是别的这些问题任何一个没想清楚提交上去就是红红的“答案错误”。PTA这平台我用了这么多年最大的特点就是判题极严、格式极刁但这也正是它的价值所在——逼着你把细节抠到极致。这道题适合的人群很明确刚开始学C语言循环结构的大学生、准备期末上机考试的考生、以及想在PTA上刷基础分但老是被格式卡住的新手。它看起来简单却是检验你有没有真正理解循环和浮点数的一个好试金石。2. 公式展开与近似思路为什么还要单独讲一遍数学先把这个公式彻底看透。自然常数 e 的定义有很多种这道题用的是最常见的级数展开e 1 1/1! 1/2! 1/3! 1/4! ...也就是说第 n 项从0开始数的话是 1/n!。注意第一项是 1/0!0的阶乘定义上是1所以整个求和从 1 开始加。这里有个很多初学者没反应过来的点题目里写的“直到最后一项小于给定精度”到底指哪一项是指 1/n! 这一项本身小于 eps而不是指累加和的变化量小于 eps。这两个概念要分清。前者是判定当前项太小、对结果影响可以忽略后者是看总和的增量。这道题明确要求按前者来处理也就是你循环里比较的是item当前项和eps。举个例子如果 eps 0.00001那么你需要加到哪一项1/8! 1/40320 ≈ 0.0000248还大于1e-5 1/9! 1/362880 ≈ 0.00000276小于1e-5。所以循环应该在 n9 这一项加完之后再判断停止。换句话说9! 这一项是最后被加进去的项加完 1/9! 之后下一项 1/10! 才小于 eps这时循环停下来。这个边界逻辑如果搞反了求出来的 e 值会差一点点而PTA的精度判题会把这一点点放大到错误。还有一点值得说虽然理论上 n 越大越接近真实 e但由于 double 类型的精度有限当 n 超过 20 左右时后续项对总和的贡献在 double 的精度下已经几乎看不出来了。所以这道题你不需要担心死循环问题——只要 eps 不过分小比如1e-15以下循环项数都在可控范围内。3. 核心实现递推、变量类型与循环结构的三角关系这道题最常见的写法有两种一种是每次循环内重新计算阶乘另一种是用递推关系从前一项推出下一项。我强烈建议用递推因为它的效率更高也更符合工程上的思维习惯。具体来说阶乘的计算有个特征n! (n-1)! × n那么反过来1/n! (1/(n-1)!) / n也就是说每一项都可以由前一项“除以 n”得到。这样我根本不需要存一个不断变大的阶乘整数也不需要每次从1开始乘到n只用一个 double 变量item来表示当前项item 1.0; // 第0项 1/0! e 1.0; // 累加和初始为1对应第0项 for (n 1; item eps; n) { item item / n; // 从上一项推出当前项 e item; }这段代码的核心逻辑就是每次迭代当前项 item 通过除以 n 变成 1/n!加进 e 里。循环条件是item eps—— 只有当当前项还大于等于 eps 时才继续加。当 item 已经小于 eps 时说明这一项太小了不加退出循环。仔细看上面的代码你会发现一个细节循环里加进去的第一项是 1/1!加完之后 item 变成 1/2!再判断……等到 item 变成 1/10! 的时候它已经小于1e-5循环退出。这意味着最后加进去的是 1/9!正好符合第1节里分析的边界。变量类型这里有个大坑n 必须用 intitem 必须用 double。如果 n 用 double 确实也能算但循环变量用 double 容易在判断条件上出问题而且浪费。item 为什么一定要 double因为 1/n! 是典型的浮点数用 int 直接截断成0整个累加就废了。另外一个细节初始的 e 到底设成 0 还是 1取决于你的循环怎么组织。如果你从 n1 开始循环那么 e 的初始值应该为 1.0对应第0项。如果你把第0项也放进循环里那就要让 item 初始为 1.0e 初始为0。两种写法都行但务必保持逻辑自洽别把第0项加两次。我给一个完整的示例代码这个版本在PTA上是可以直接过的#include stdio.h int main() { double eps; double item 1.0; double e 1.0; int n 1; scanf(%lf, eps); while (item eps) { item / n; e item; n; } printf(%.6f\n, e); return 0; }这段代码的循环用的是 while其实和 for 是等价的。关键在于每次循环先更新 item 再加进 e顺序别搞反。如果你先e item再item / n那第0项会被算两次结果偏大。4. PTA输出的老规矩格式不对功夫白费很多人在这道题上算法写对了却栽在输出格式上。PTA的浮点数输出有它自己的约定这里必须单独拎出来说。题目要求“输出e的近似值”默认情况下你应该用%.6f也就是保留6位小数。这里有个容易混淆的点你求的是 e 的近似值不是每一项的近似值。有人会在循环里对每一项单独做四舍五入再累加这样会导致误差累积最后的输出和你直接在总和上保留6位小数结果可能不一样。PTA判题用的是双精度比较还是字符串比较因题目而异但你按常规思路来准没错。另外读入的 eps 用什么格式直接用%lf读 double 就可以。测试点里给的 eps 一般长这样0.00001或者1e-5%lf都能正常读进来。别用%f去读 double那是老谭浩强教材里的历史遗留问题在 X86 平台上可能碰巧能过但在严格判题的环境下属于未定义行为能用对就尽量用对。还有一个老生常谈的点printf 的格式串里别加多余的空格或换行。PTA很多题目要求输出后跟一个换行但也有的是多个输出用空格隔开。这道题一般只输出一个数所以你只需要printf(%.6f\n, e);就好。如果你多加个空格在格式串前面比如printf( %.6f\n, e);提交上去很可能出现“格式错误”。从另一个角度说PTA的格式错误提示其实很有帮助。我第一次提交这道题的时候就是因为输出多了一个空格卡了很久教训惨痛。从那以后我养成一个习惯读题目里的输出样例仔细看它有没有空格、有没有换行、小数位是几位。这叫“所见即所得”样例是什么样你输出就得是什么样。5. 从零开始的完整解题流程一道题走到底现在我把这道题的完整解题流程串一遍从读题到提交给你一个可以直接照做的路径。第一步读题圈出关键信息。注意题目要求是“最后一项小于 eps 时停止”还是“计算到某一项小于 eps 就不再累加”。这个表述上的细微差别决定了你循环体里的判断顺序。这道题通常是“直到最后一项小于给定精度”意味着加进总和的那一项还大于等于 eps。第二步确定累加变量的初始值。e 的级数展开从第0项开始是 1/0!1所以总和初始为1。如果你把第0项单独拿出来初始化循环就从 n1 开始如果你想统一处理就设 item1.0e0然后循环 n0 开始。两种都行看你喜欢哪种。第三步设计循环结构。我习惯用 while 或 for 都行核心是 item 的更新必须在累加之前。每次循环体做的事是更新 item 为 1/n!累加到 en 自增。循环条件用 item eps注意这里的 eps 是你从输入读到的精度阈值。第四步输出并自测。用几组典型 eps 测试一下eps1 时循环里加 1/1! 那一项时 item1e 从1变成2接着 item 变成0.5小于1退出输出2.000000这是对的因为 e 的近似值在第一项之后的2.0确实满足误差要求。你还可以用 eps0.000001 测一下输出应该是 2.718282我记得这个值但以你机器实测为准。第五步提交。提交前检查有没有用 scanf 读入、有没有漏掉 return 0、有没有输出换行。这几个是PTA零分最常见的元凶。6. 常见问题排查为什么别人能过我不能这道题在PTA的讨论区里提问频率最高的几个问题都很有代表性我逐个说一下排查思路。问题一输出结果总是 2.000000不管 eps 怎么变。这基本可以肯定是循环没进去或者 item 初始值不对。如果 item 初始设为0那item eps不成立循环一次都没执行e 保持初始值1输出1.000000。如果 item 初始设为1且是 int 类型那么item / n是整数除法从第二项开始全是0e 一直累加到2就不再增长输出2.000000。排查思路打印中间值看 item 的变化是不是 1, 0.5, 0.1667, 0.0417... 这个序列。问题二答案错误但是和标准答案就差在最后一位小数上。这种情况十有八九是你循环多跑了一项或者少跑了一项。回溯一下你的循环条件是item eps还是item eps一个是大于等于就继续一个是严格大于才继续。边界条件下item 恰好等于1e-5两种判断会对结果产生微妙影响。PTA的标准解法一般用你按这个来就好。另外检查你是不是先累加再判断退出这样会导致多加一项边缘项。问题三直接显示“编译错误”。大概率是你把变量声明写在了语句之后C89标准不支持或者用了//注释但编译器不支持。PTA的判题环境一般用 gcc默认标准是 GNU C如果没有特殊说明你在 main 函数中间声明变量也是可以的。不过保险起见我建议所有变量都声明在函数开头这样最稳。问题四格式错误明明输出看起来和答案一模一样。试着查看完整输出看看末尾有没有多一个空格。把printf(%.6f\n, e);改成printf(%.6f, e);看提示是否变化。如果题目要求换行而你没加也会报格式错误。还有一种可能题目要求保留6位小数但你用了%f默认6位其实是对的但如果你用了%.6f也没有问题。真正容易错的是你用%d或者%g输出了一个浮点数。我把这些常见问题的排查要点整理成一个速查表方便你对照症状可能原因检查方向输出恒为 1.000000item 初始为0循环没进去item 初始化为 1.0输出恒为 2.000000item 是整数类型累加全变0item 声明为 double结果偏大/偏小一位先累加后更新或判断条件用反调整循环体顺序报错格式错误多空格、缺换行对照输出样例逐字符检查报错段错误scanf 传参没有取地址scanf(%lf, eps)7. 这道题背后的东西从PTA题目到工程思维说句实在话求 e 的近似值这个任务在实际工程里根本不会有人用泰勒展开去算——标准库里的 exp(1) 又不花钱。但PTA为什么要把这道题放进基础实验因为它考察的不是数学计算而是三个底层能力循环不变式、递推关系优化、浮点边界意识。循环不变式是什么意思你在写循环的时候得想清楚每一次循环开始时 item 代表的是什么。是“下一次待累加的值”还是“当前已经累加进去的值”两种定义决定了代码完全不同的写法也决定了边界条件。很多同学写代码靠“试一试过了就行”结果换一个 eps 就出问题原因就是没有建立这个不变量。递推关系优化就更值钱了。用item / n代替每次算阶乘复杂度从 O(n!) 变成 O(n)在 n 很小的时候看不出差别但如果你把这个思路推广到其他级数求和、动态规划、递推数列那就是算法优化的基本素养。我看过不少同学写这段代码每次循环都调一个 factorial 函数虽然也对但明显缺乏对项之间关系的观察。浮点边界意识这个是最重要的。浮点数判断相等是禁忌这个大家都知道可很少有人意识到浮点数的和在精度临界点上的微妙差别。这道题里eps 取个像 0.00001 这样的值item 在某个 n 之后会发生跳变可能直接跨过 eps 而没有经历“等于”的过程。这时候用和用的结果可能完全一样但在别的题目里边界点会要了你的分。还有一点是关于编码习惯的。我见过很多同学的代码变量全用 a、b、c 命名循环条件写得像天书最后自己都看不懂。你写item、eps、e、n这种可读性强的名字不仅是为了让老师看懂更是为了让你自己三天后重新打开还能明白当时的思路。这道题就是一个很好的练手机会——代码很短但处处体现命名、结构和注释的分寸。8. 附带说一说PTA平台上的其他常见坑借着这道题我顺手分享几个PTA平台通用的经验这些不只是针对求e这道题对你在PTA上刷其他题也一样适用。第一个经验是看清题目里的时间限制和内存限制。PTA有些题虽然结果对但超时或超内存同样不通过。这道题显然不会有这些问题但你在做“二分查找pta函数”“复数四则运算c语言pta”这类题的时候复杂度分析就变得重要了。能用循环就用循环别动不动递归除非题目明说要递归。第二个经验是善用本地调试别直接在PTA上瞎试。每次提交都要等判题结果效率太低。我的习惯是把样例输入复制到本地跑一遍再用自己的边界测试数据多跑几遍确保无误后再提交。这道题你可以自己测试 eps1、eps0.1、eps0.00001 这几组数据看看输出是不是符合预期。第三个经验是读“讨论区”。PTA每道题下面都有讨论区很多同学已经把踩过的坑发上去了。这不算抄作业学会从别人的错误中吸取教训也是一种重要的工程能力。第四个经验是从“PTA题库答案c语言”这类搜索里找到的东西一定要自己过一遍脑子。网上确实有大量题解但如果你只是复制粘贴那你什么都没学会。更关键的是很多网上的题解是错的或只过了某组测试点你抄了反而挂科。用自己的思路写一遍再对照别人的精简方案看差距在哪儿这才是正确姿势。说回这道求e的题它还有一个变体是要求输出“累加项数 n”那你就得在循环外面定义 int cnt0每累加一次 cnt最后输出两个值。这个变体更简单地考察了你对循环计数的敏感性。如果之后遇到类似变体把所有输出用空格隔开最后换行基本就能过。我个人的体会是PTA的实验题往往越简单越容易粗心求e的近似值这道题看起来是“送分题”但实际上好好做一遍你能理清循环结构、递推优化、浮点输出、边界条件这四件事。如果你能不看任何参考完全靠自己的思考把这道题AC并且能解释清楚为什么 item 要声明成 double、为什么判断条件用 、为什么输出用 %.6f那你对C语言基础部分的掌握就已经超过大多数同龄人了。以后在PTA上刷到天梯赛L2级别的题你也不会因为基础不牢而吃亏。
返回列表