首页 / 资讯中心 / 文章详情

PTA L1-108零头就抹了吧:C++浮点数取整、类型转换与精度避坑指南

PTA L1-108零头就抹了吧:C++浮点数取整、类型转换与精度避坑指南 ★ FEATURED ARTICLE
刷到 L1-108 这道题的时候大部分人的第一反应是这不就是输出整数部分嘛。确实零头就抹了吧在 PTA 的 L1 系列里属于典型的送分题核心考察一个再基础不过的 C 操作——把浮点数的零头去掉保留整数部分。但送分题不等于不会失分我见过不少朋友在这道题上栽跟头有人被浮点精度坑了有人没看清四舍五入和直接舍弃的区别还有人输出格式不对导致判题直接给了个没分。这篇文章就针对这道题做一次完整拆解从题目到底想考你什么到几种实现方案的取舍再到实战代码和常见踩坑点一次讲清楚。不管你是刚学 C 的新手还是准备比赛刷题的选手看完这部分内容用不了十分钟就能稳稳拿下。1. 题目在考你什么先把抹零这个词翻译成代码逻辑1.1 这道题在 PTA L1 系列里的定位PTA 的 L1 题目一般都默认你刚学完语法还没接触什么复杂算法。它考察的往往不是思维难度而是你能不能把一句大白话准确翻译成程序指令。零头就抹了吧就是非常典型的一例——题目描述里可能会给你一个购物金额、一笔账单、或者一个浮点数要求你把零头抹掉只保留整数部分。这个过程在数学里叫向下取整在编程里最简单的实现就是直接把浮点数转换成整数类型。很多新手一上来觉得这种题简单直接cout (int)x;就交了结果发现题目要求里藏着别的东西。比如有的版本要求抹零指的是把小数点后全部舍去也就是截断有的版本要求四舍五入到整数还有的版本可能进一步要求抹到十位、百位。所以第一步不是写代码而是把抹零这两个字的真实含义在脑海里翻译成数学操作。要是翻译错了后面写得再漂亮也是白搭。1.2 抹零的几种常见版本日常生活中的抹零很简单一顿饭 128.7 元老板说零头抹了你就付 128 元。程序里的抹零通常对应以下几种操作直接截断小数部分不做四舍五入123.99输出123四舍五入到整数123.5输出124123.4输出123抹到十位或百位123.0抹到十位输出120抹到百位输出100抹掉末尾的.0如果输入是123.00输出123我在实际做题时见过的最常见版本是第一种也就是把浮点数的小数部分直接丢掉。但为了稳妥拿到题目后先看输入输出样例样例是最靠谱的翻译器。如果样例里输入了123.456输出123那就是截断如果输入123.50输出124那就要四舍五入。多看两眼样例能少踩一半的坑。1.3 把自然语言翻译成程序条件无论哪种版本解题逻辑都能拆成三步读入数据、处理数据、输出数据。以最常见的截断版本为例用程序语言描述就是输入一个浮点数或一个表示金额的字符串去掉小数部分直接舍弃不四舍五入输出处理后的整数这里有个隐藏问题输入到底是什么类型如果题目告诉你输入是一个实数那double接收没问题但如果输入是一个类似123.456的字符串或者数值非常大直接读成double就可能出问题。后面我会专门说这个问题。总之先把题目给出的所有条件列出来确认清楚再动笔。注意PTA 对格式要求很严格哪怕输出结果完全正确缺了换行符或者多输出一个空格都可能判错。这类基础题往往是题目简单格式杀人。2. 三种实现思路从最直觉到最稳妥2.1 最直觉的做法double 强转 int先看最常见的写法#include iostream using namespace std; int main() { double x; cin x; int result (int)x; cout result endl; return 0; }这个代码的核心就是(int)x。C 里把一个浮点数强制转换成整数时会直接把小数部分砍掉不做四舍五入。比如(int)3.99的结果是3(int)-3.1的结果是-3注意不是-4这是截断而不是真正意义上的向下取整。这个方案最大的优点是简单直白两行就搞定。但它的隐患也很明显第一如果输入的数值非常大超过了int的表示范围转换结果就是未定义行为可能输出一个莫名其妙的垃圾值第二浮点数在计算机里的存储本身就不是绝对精确的某些情况下会出现意想不到的偏差。所以这个写法只建议在输入范围明确、数值不大的情况下使用。2.2 更稳的思路用字符串处理如果你不想和浮点精度较劲另一个思路是直接把输入当成字符串读进来然后找到小数点输出小数点前面的部分#include iostream #include string using namespace std; int main() { string s; cin s; for (char c : s) { if (c .) break; cout c; } cout endl; return 0; }这个代码的逻辑是遍历字符串遇到小数点就立即停止把前面的字符全部输出。比如输入123.456会输出123输入42没有小数点会输出完整的42。字符串处理的好处是彻底绕开了浮点精度问题输入的数值再大、小数部分再长也不怕。因为题目给的金额从某种意义上说就是个字符串你只是把它按.切开取前面那一段而已。缺点是代码比强转多几行而且如果你后续还想做四舍五入字符串处理反而要写更多判断。所以到底选哪种取决于题目要求。2.3 还有一种思路读入后按位运算如果你要抹到十位、百位直接在整数层面操作会更快#include iostream using namespace std; int main() { double x; cin x; int result (int)x / 10 * 10; cout result endl; return 0; }这里(int)x先取整数部分比如123.89得123123 / 10整除得1212 * 10得120。整除运算本身就把个位抹掉了。这种位权思维很重要以后做取整、进位、截位相关的题都会用到。它的本质是你想抹掉哪一位就先除以对应的位权做整除然后再乘回来。这三种思路各有适用场景。我的建议是先看题目给的数值范围再看输出样例最后决定用哪种。别一听最简单就直接上强转万一输入是个超大金额强转当场翻车。2.4 三种方案对比方案适用场景优点风险double 强转 int数值小、明确要求截断代码少、理解容易大数溢出、浮点精度可能出偏差字符串截断输入可能很大、要保留所有位精度稳、逻辑直观处理四舍五入或位权时稍麻烦整数除法运算要求抹到十位/百位简单高效、思路通用必须先转成整数同样有精度问题3. 实操代码与逐行解析从提交到拿满分3.1 先写注释再写代码刷这种基础题我最推崇的做法是先写注释后写代码。哪怕是在在线判题系统的编辑器里也先在脑袋里或者草稿纸上把这个流程理一遍。比如针对最常见的截断版我通常会写下这样的注释// 1. 读入一个浮点数 // 2. 取整数部分截断小数 // 3. 输出整数有了这三行注释代码框架就出来了。具体实现如下#include iostream using namespace std; int main() { double price; cin price; int result static_castint(price); // C 风格的强制转换 cout result \n; return 0; }我用static_castint(price)而不是(int)price纯粹是因为读者以后可能看到这种写法。C 风格的强制转换在 C 里依然合法但 C 风格更明确。运行结果输入23.99输出23输入100.0输出100。如果你担心输出效率把endl换成\n即可。endl不仅换行还会刷新输出缓冲区在这里影响不大但数据量大的时候\n更快。3.2 处理字符串版本的边界情况如果你选择字符串方案有几个边界情况必须处理好。首先是输入没有小数点比如42。上面的循环会一直遍历到字符串结束输出42没有问题。其次输入可能是0.5循环遇到小数点就退出输出0也没问题。还有一种情况是负数虽然金额一般不会是负数但题目若不特别说明严谨一点可以考虑。负数情况比较麻烦-123.45字符串方案会输出-123而(int)(-123.45)在 C 里结果是-123两者看起来一致。但要注意(int)(-123.9)的结果是-123这在数学上叫向零取整而不是向下取整。如果你的题目明确要求向下取整负数场景就得用floor函数了。不过 L1 级别的题目几乎不会出负数金额这里就当留个知识储备。3.3 遇到四舍五入的版本怎么办假设题目不是截断而是要求四舍五入到整数。方法有三万能公式(int)(x 0.5)对正数有效。调用标准库round(x)C11 起在cmath里正负数都能正确处理。自己写判断看小数部分是否大于等于 0.5。用公式(int)(x 0.5)的原理是四舍五入的规则就是超过一半进一位所以给原数加 0.5 再截断就相当于把 0.5 这个分界线变成整数分界线。比如3.4 0.5 3.9截断后是33.5 0.5 4.0截断后是4。这个技巧在很多题目里能用上。用round则更省心#include iostream #include cmath using namespace std; int main() { double x; cin x; cout round(x) \n; return 0; }不过round返回的是double输出时如果值是4.0cout默认可能会打印成4但如果题目要求整数格式这样做没问题。如果担心输出变成4还是4.0可以先转long long再输出。3.4 抹到十位和千位的实现技巧最后说扩展。如果题目升级成零头就抹了吧十位版也就是任何金额都只保留到十位个位直接抹掉那么核心代码就是long long n; cin n; // 如果输入是整数直接读整数 long long result n / 10 * 10; cout result \n;输入123输出120输入998输出990。如果输入是浮点数先截断成整数再同样处理即可。这种整除-乘回的技巧本质上是在做取整运算你可能在任何和数字打交道的题里遇到。4. 新手最容易踩的坑这些细节真的会扣分4.1 浮点精度问题0.29 乘 100 竟然等于 28.999999我认识的好几位同学都在这上面吃过亏。假设题目要求把输入的小数部分抹掉但输入的值是0.29。你用cout (int)(0.29 * 100);结果居然是28而不是29。原因在于计算机用二进制表示小数很多十进制小数无法被精确表示。0.29在内存里是个非常接近 0.29 但不完全相等的数乘 100 后可能是28.999999...再强转成int直接变成28。解决办法有几种不要做乘法和强转这种容易放大误差的组合操作尽量只做一次强转。如果必须乘法给结果加上一个很小的数比如(int)(0.29 * 100 0.0000001)。更彻底的方案不需要参与运算的话直接用字符串处理从源头避开浮点。记住一句话浮点数适合比较不适合做精确的金额计算。这也是为什么真实世界的财务系统都用整数分而不是浮点元。4.2 读题不仔细截断和四舍五入是两回事这是这类题最大的失分点。题目写零头就抹了吧看起来像是直接舍弃但如果样例显示2.5输出3那明显是四舍五入。很多时候不是你不会写四舍五入而是根本没意识到这里要四舍五入。我的建议是拿到题目后把输入输出样例用笔抄一遍手动算一遍。样例怎么算代码就怎么写。不要凭经验脑补题目意思。4.3 输出格式换行、空格、整数还是浮点判题系统对输出格式极其敏感。常见格式丢分情况有忘记换行。PTA 几乎都要求每个输出占一行没换行直接判错。输出了多余的空格。比如输出123后面多一个空格字符层面就对不上。该输出整数却输出了浮点。比如cout (double)3;可能输出3有些编译器配置下可能会显示3但如果你用printf(%.0f, 3.0)输出3某种情况下也可能 OK不过最稳妥的还是明确输出整数类型。多个测试数据之间没换行挤在一起。解决方式很简单输出后用\n结尾整数就用long long或int输出别在输出里画蛇添足。4.4 常见问题速查表症状可能原因解决方案大数输出错误int 范围不够改用 long long 或字符串乘法后结果少 1浮点精度丢失加极小量 epsilon或避免乘后强转样例能过交上去错没看完整题目要求仔细读题中四舍五入/截断/取整字眼输出格式错误缺换行、多空格、类型不匹配用\n输出前检查类型字符串方案输出负数异常没处理负号单独判断-开头的输入5. 从这道基础题延伸出去通用的做题方法论5.1 先找边界再写逻辑做 L1 这类基础题最快的流程是先读一遍题目画出关键条件再对着样例手动算一遍确认是截断还是四舍五入最后写代码写完立刻用样例试一遍再用几个自己构造的特殊值试一遍。特殊值怎么构造边界值优先。比如0、一个很大的数、一个小数点后全是 9 的数、一个整数格式的输入。这些值最容易暴露问题。拿这题来说我会试0.999、1000000.123、42这三种输入基本能把代码问题都找出来。5.2 一个可以直接套用的通用模板下面这个模板可以覆盖这道题 90% 的情况包括截断和四舍五入两版#include iostream using namespace std; int main() { double n; cin n; // 如果需要四舍五入把这一行换成 n round(n); long long ans static_castlong long(n); cout ans \n; return 0; }如果你的题目输入是字符串且数值极大把double换成string再用遍历到.的方式截断。这两种核心模板记下来遇到取整数抹零取整截断这类题都能快速套用。5.3 再往后走这题还能引出什么别小看这种送分题它背后牵出的知识点其实很密集。类型转换隐式转换、强制转换、截断与向下取整的区别、浮点数精度、标准库函数floor、ceil、round、字符串解析、位权思想。这些概念在后续的题里会反复出现。比如你学会了字符串截断就能处理身份证提取出生日期、电话号码分段、IP 地址解析这类字符串处理题。你学会了n / 10 * 10就能处理进制转换、数字反转、数位分离这些经典题目。基础题不是刷完就完了把它当成一个起点把每个细节吃透后面的进阶题会轻松很多。我在实际刷题中有一个体会真正让你在比赛里拉开差距的不是会不会写难题而是简单题能否一次通过。那些基础题如果能做到样例即过、边界稳过、格式全对就能把省下来的时间留给真正有挑战性的题目。零头就抹了吧这种题应该是你拿满分的底气而不是纠结的源头。
阅读完成 · 觉得有帮助?
咨询建站