C/C++ 保留小数全攻略:从printf到setprecision
C/C 保留小数全攻略从printf到setprecision前言写代码的时候经常遇到保留小数的问题——输出要保留两位小数、计算要四舍五入、有时候还要控制有效数字位数。C和C在这方面的处理方式不太一样C有printf那一套格式控制C则有cout配合iomanip。本文把这些方法整理了一下希望能帮你一次性搞清楚。一、C语言的方法1. printf 格式化输出这是C语言最常用的方式通过%f格式符配合精度修饰符来实现。基本语法%.nf其中n表示要保留的小数位数。例如printf(%.2f,3.14159);// 输出 3.14如果不指定精度%f默认保留6位小数。进阶用法%m.nfm表示整个输出占用的最小宽度包括整数部分、小数点和小数部分n表示小数位数。如果实际宽度小于m左边会补空格。printf(%8.3f,3.14159);// 输出 3.142共8位前面3个空格注意事项单精度float的有效位数大约是7位双精度double大约是16位。如果要求输出的小数位数超出有效位数结果可能会出错。printf的%.nf是自带四舍五入的。不过有个小细节保留0位小数时只要小数点后第一位 5 就进位但保留1位或更多位时需要保留位的下一位 5 才会进位。这其实是浮点数二进制表示导致的后面会细说。其他格式符格式符说明%f十进制小数形式默认6位小数%e/%E指数形式如1.23e02%g/%G自动选择%f或%e去掉多余的02. sprintf 先格式化再使用有时候不只是输出还需要把格式化后的结果存起来供后续使用。这时候可以用sprintf。charbuffer[50];sprintf(buffer,%.2f,3.14159);// buffer 中存的是 3.143. round 函数进行四舍五入printf只是控制显示并不会改变变量本身的值。如果需要在计算中真正得到一个四舍五入后的数值可以用round函数。round函数定义在math.h中C99标准后可用doubleround(doublex);floatroundf(floatx);longdoubleroundl(longdoublex);round(3.623)返回4.0。但round只能四舍五入到整数如果要保留n位小数需要先乘再除doubleroundToNDecimalPlaces(doublenum,intn){doublefactorpow(10,n);returnround(num*factor)/factor;}编译时需要链接数学库gcc -lm。手动实现四舍五入不依赖math库// 保留2位小数doubleresult(int)(num*1000.5)/100.0;加0.5再强转取整本质就是四舍五入。不过要注意浮点数精度问题这种方法在某些边界情况下可能不准确。二、C的方法C 继承了一部分C的方式printf仍然可用但更推荐用 cout 配合iomanip头文件中的操纵符。1. setprecision fixed最常用需要包含iomanip头文件。#includeiostream#includeiomanipusingnamespacestd;intmain(){doublenum3.1415926;coutfixedsetprecision(2)numendl;// 输出 3.14return0;}fixed固定小数点格式不用科学计数法setprecision(n)设置小数点后的位数为 n两者结合使用自动补0且四舍五入三种写法// 写法一coutsetiosflags(ios::fixed)setprecision(2);// 写法二cout.setf(ios::fixed);coutsetprecision(2);// 写法三最简洁coutfixedsetprecision(2);三种写法效果一样。注意setprecision设置一次后对后续所有浮点数输出都有效直到被重新设置。如果需要恢复默认可以cout setprecision(6)。2. 只用 setprecision保留有效数字如果不加fixedsetprecision(n)控制的是有效数字的总位数整数部分 小数部分。doublenum1234.56789;coutsetprecision(5)numendl;// 输出 1234.65位有效数字同样会四舍五入不会自动补0如果整数部分位数超过 n会改用科学计数法3. scientific科学计数法coutscientificsetprecision(3)numendl;// 输出 1.235e03setprecision(3)控制的是小数点后的位数。4. C 中使用 printfC 完全兼容 C 的printf用法和C语言一样printf(%.2f\n,3.14159);// 输出 3.14printf(%.2lf\n,3.14159);// double 用 %lf5. stringstream 格式化需要把格式化结果存成字符串时可以用stringstream#includesstream#includeiomanipstringstream ss;ssfixedsetprecision(2)3.14159;string resultss.str();// result 3.146. C 中的 roundC 同样可以使用round函数包含cmath即可。用法和C完全一样。#includecmathdoubleresultround(3.14159*100)/100.0;// 3.14三、实际开发中的坑坑1浮点数精度问题浮点数在计算机里是二进制表示的很多十进制小数无法精确表示。比如printf(%.2f,2.675);// 可能输出 2.67 而不是 2.68这不是printf的 bug而是2.675在二进制中实际存储的值略小于2.675。如果对精度要求极高比如金融计算建议用整数分或者专门的十进制库。坑2float 和 double 的区别用printf时float用%fdouble用%lf。但在printf中float会自动提升为double所以用%f也能打印double。不过为了代码清晰建议该用什么就用什么。C 的cout不区分float和double统一处理。坑3setprecision 的持久性setprecision设置后会一直生效这在有些场景下是好事不用重复写但也可能造成意料之外的输出coutfixedsetprecision(2)3.14159endl;// 3.14cout2.71828endl;// 2.72仍然保留2位小数如果想恢复需要重新设置。坑4round 的银行家舍入C11 起round遵循远离零的舍入规则。但某些语言的round是银行家舍入四舍六入五成双跨语言开发时要注意。坑5VS2022 的不安全函数警告C4996如果你在 Visual Studio 2022 里写 C/C 代码大概率遇到过这样的报错error C4996: sprintf: This function or variable may be unsafe. Consider using sprintf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS.不只sprintfscanf、strcpy、gets这些函数都会触发同样的警告。VS2022 默认启用了一套安全检查机制Security Development LifecycleSDL把这类传统 C 库函数标记为不安全或已弃用。为什么这些函数不安全根本原因是它们不检查目标缓冲区的大小。比如scanf(%s, arr)读字符串时如果用户输入了超过arr长度的内容数据就会越界写入可能导致程序崩溃甚至被利用执行恶意代码。sprintf也是类似的问题——它只管往缓冲区里写不管缓冲区够不够大。三种解决方式方式做法优点缺点使用安全版本函数把sprintf换成sprintf_sscanf换成scanf_s最安全符合微软推荐这些函数是 VS 特有的C11 Annex K 可选规范代码移植到 Linux 或 GCC 环境可能无法编译定义_CRT_SECURE_NO_WARNINGS在文件最开头所有#include之前加一行#define _CRT_SECURE_NO_WARNINGS代码保持标准 C/C 写法可移植性好禁用了警告需要自己确保输入安全项目属性中关闭项目属性 → C/C → 预处理器 → 预处理器定义中添加_CRT_SECURE_NO_WARNINGS整个项目统一配置不用每个文件都写同上一点建议写作业/学习算法用#define _CRT_SECURE_NO_WARNINGS最简单省事把精力放在逻辑上。写正式项目/跨平台代码优先考虑用snprintf代替sprintf——它是 C99 标准函数既安全又跨平台。或者干脆不用scanf改用fgetssscanf组合更可控。初学阶段不建议一上来就用_s版本因为换了个编译器可能就不认识了容易产生困惑。另外还有一个细节如果不想每次新建.c文件都手动加一行宏定义可以修改 VS2022 的默认模板文件newcfile.cpp把#define _CRT_SECURE_NO_WARNINGS写进去以后新建文件就自动带上了。坑6宽字节环境wchar_t下的格式化输出如果只是写控制台算法题基本碰不到宽字节。但要是搞 Windows 开发MFC、COM、Win32 API 或者用wchar_t做国际化字符串经常是宽字符类型的保留小数的写法就有变化了。C 风格宽字节输出用wprintf替代printf格式字符串也要加L前缀doublenum3.14159;wprintf(L%.2f\n,num);// 输出 3.14wprintf对%f、%lf的支持和printf一样但有个坑wprintf默认可能无法正确输出非 ASCII 字符比如中文单位或提示语这个和终端编码有关Windows 下是 UTF-16 但控制台默认是 GBK一般不推荐在 Windows 控制台里用wprintf混着打印中文和数字。C 风格宽字节输出用wcout替代cout操纵符用法完全一致doublenum3.14159;wcoutfixedsetprecision(2)numendl;// 输出 3.14转字符串时的参数顺序陷阱printf对应的宽字节版本是wprintfsprintf对应的是swprintf。但这里有一个跨平台的大坑Linux / 标准 CC99swprintf的签名是int swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...);必须传缓冲区长度。Windows旧版 CRTswprintf的签名是int swprintf(wchar_t *buffer, const wchar_t *format, ...);跟sprintf一样不检查长度直到 VS2015 后微软才改了实现让它兼容标准行为。这就导致同样一行swprintf(buf, L%.2f, num)在 Linux 上能编译在旧版 VS 上也能编译但参数含义完全不同——Linux 把L%.2f当长度参数解析直接报错或崩溃。好在 VS2022 里swprintf已经按标准实现了但为了保险微软更推荐直接用swprintf_sswprintf_s(buf,sizeof(buf)/sizeof(wchar_t),L%.2f,num);这就又回到坑5里说的问题了——_s版本是 VS 特有的换到 GCC / Clang 环境就编译不过。区域设置导致小数点变成逗号这算是宽字节环境下最隐蔽的坑。如果代码里为了输出中文提示语而调用了setlocale(LC_ALL, zh_CN.UTF-8)或类似的操作浮点数输出的分隔符可能从.变成,——欧洲很多国家用逗号当小数点。实测例子#includeiostream#includeiomanip#includeclocaleusingnamespacestd;intmain(){doublenum3.14;setlocale(LC_ALL,zh_CN.UTF-8);coutfixedsetprecision(2)numendl;// 可能输出 3,14return0;}解决办法就两种保持区域设置为C或en_US.UTF-8中文输出单独处理比如用MultiByteToWideChar手动转或者分开不同流。如果必须用中文区域输出数字时临时切回去char*old_localesetlocale(LC_ALL,NULL);setlocale(LC_ALL,C);coutfixedsetprecision(2)num;// 用 C 区域输出数字setlocale(LC_ALL,old_locale);// 切回去输出中文不过这种做法比较繁琐实际开发中更推荐用printf/wprintf配合格式字符串不受区域设置影响。VS2022 对宽字节_s函数的额外检查和sprintf_s一样swprintf_s对缓冲区大小检查极其严格。如果数值很大比如1e20保留 2 位小数后要占用几十个字符超过了buf长度swprintf_s不会像旧版函数那样悄悄截断而是直接触发invalid parameter异常调用invalid_parameter_handler。所以用的时候缓冲区宁可开大一点比如wchar_t buf[512]或者先用_scwprintf计算所需长度intlen_scwprintf(L%.2f,num);// 计算格式化后需要的字符数wchar_t*buf(wchar_t*)malloc((len1)*sizeof(wchar_t));swprintf_s(buf,len1,L%.2f,num);这样就不会因为长度不够而踩坑了。宽字节场景实用建议场景推荐做法只是输出到控制台直接用printf/cout省心。真遇到宽字符串需要打印转成char*再输出需要把格式化结果存进wchar_t*用swprintf_sVS或snprintf转成char再转宽字节跨平台最稳妥跨平台项目Windows Linux尽量避免用swprintf统一用snprintf处理成char字符串最后再转wchar_t参数顺序不会打架区域设置影响小数分隔符数字输出不受区域影响的方法用printf/sprintf系列它们不依赖setlocale的LC_NUMERIC除非显式设置cout/wcout则受std::locale影响要格外留意这个坑虽然不常遇到但一旦碰到了查错成本特别高——因为编译能通过运行结果却莫名其妙多出来一个逗号。希望你能少走弯路。四、方法对比表格方法语言头文件是否四舍五入是否补0是否改变原值适用场景printf(%.nf)C / Cstdio.h✅✅❌直接输出sprintfC / Cstdio.h✅✅❌转字符串fixedsetprecisionCiomanip✅✅❌cout输出仅setprecisionCiomanip✅❌❌控制有效数字scientificsetprecisionCiomanip✅✅❌科学计数法stringstreamsetprecisionCsstreamiomanip✅✅❌转字符串round(num * factor) / factorC / Cmath.hcmath✅取决于输出格式✅计算中需要精确值手动(int)(num*1000.5)/100.0C / C无✅取决于输出格式✅不想引入math库五、结语总结一下个人经验只是输出C用printf(%.nf)C用cout fixed setprecision(n)简单直接。需要字符串C用sprintfC用stringstream。计算中需要四舍五入的值用round(num * pow(10,n)) / pow(10,n)。控制有效数字C用单独的setprecision(n)C用%g。VS2022用户要么在文件开头加#define _CRT_SECURE_NO_WARNINGS要么老老实实用_s版本。宽字节场景能用printf就别用wprintf能用char*就别碰wchar_t*实在绕不开就用snprintf打好再转参数顺序不会出幺蛾子。没有哪种方法是万能的根据场景选合适的就行。希望这篇文章对你有帮助。