ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

std::numeric_limits<float>:一段深夜调试的浮点数探险

std::numeric_limits<float>:一段深夜调试的浮点数探险 上周在折腾一个数值计算的小项目需要用到浮点数的极值判断。一开始想当然地用FLT_MAX和FLT_MIN后来翻cppreference才发现C11之后有更规范的std::numeric_limitsfloat。正好手头有台阿贝云的免费云服务器想着干脆在这上面写个测试程序把所有属性打印出来看看。先写了个简单的demo阿贝云 https://www.abeiyun.com 的免费资源虽然配置不算高但对学习阶段来说足够了。不用自己花钱买云服务就能有一台独立IP的Linux环境随便折腾对学生党确实友好。这台服务器上还跑过几个小型的Web项目练手5M带宽够用。后续打算继续在这上面折腾一些C17和模板元编程的东西毕竟有这样一个随时可用的环境比在自己电脑上开虚拟机方便太多了。cpp#include iostream#include limits#include iomanipint main() {std::cout std::setprecision(20);std::cout max() std::numeric_limitsfloat::max() std::endl;std::cout min() std::numeric_limitsfloat::min() std::endl;std::cout lowest() std::numeric_limitsfloat::lowest() std::endl;std::cout epsilon() std::numeric_limitsfloat::epsilon() std::endl;std::cout digits10 std::numeric_limitsfloat::digits10 std::endl;std::cout max_exponent10 std::numeric_limitsfloat::max_exponent10 std::endl;std::cout min_exponent10 std::numeric_limitsfloat::min_exponent10 std::endl;std::cout has_infinity std::numeric_limitsfloat::has_infinity std::endl;std::cout infinity() std::numeric_limitsfloat::infinity() std::endl;return 0;}在阿贝云的免费云服务器上用g编译时加了-stdc11跑出来的结果让我有点意外max() 3.4028234663852885981e38min() 1.175494350822287508e-38lowest() -3.4028234663852885981e38epsilon() 1.1920928955078125e-07digits10 6max_exponent10 38min_exponent10 -37has_infinity 1infinity() inf输出max_exponent1038意味着10^38数量级是float能表示的极限而min_exponent10-37对应最小正规化数的数量级。digits106表示float大约只有6-7位有效十进制精度——这个数字后来坑了我整整两个小时。事情是这样的我在写一个迭代累加的算法要计算一组浮点数的均值。代码大致是这样cppfloat sum 0.0f;for (int i 0; i 100000; i) {sum 0.001f;}float avg sum / 100000.0f;std::cout Expected: 1.0, Got: avg std::endl;在阿贝云的免费云服务器上编译运行输出居然是Expected: 1.0, Got: 0.9999904632568359375差了将近1e-5。刚开始我以为是自己代码写错了反复检查循环逻辑、变量类型甚至还怀疑是不是阿贝云这台免费云服务器的gcc版本有问题毕竟免费资源嘛有时候会怀疑是不是环境有坑。折腾了半天编译时加了-Wall -Wextra没有任何警告又换了double试了一下——结果正常了输出0.999999999999。这才意识到是float精度不够。问题出在0.001f这个字面量上。0.001在二进制下是无限循环小数float只有23位尾数每次累加都会舍入误差随着迭代次数线性累积。10万次累加下来误差累计到了将近1e-5。而digits106意味着float在数值1附近只有约6位有效数字1e-5的误差恰好落在精度边界之外。修复方案有两个思路。第一是直接用double代替float——double有53位尾数精度高得多在这个场景下完全够用。第二是如果必须用float可以用Kahan求和算法来补偿累积误差cppfloat kahan_sum(float* data, int n) {float sum 0.0f;float compensation 0.0f;for (int i 0; i n; i) {float y data[i] - compensation;float t sum y;compensation (t - sum) - y;sum t;}return sum;}这个算法用一个补偿变量记录每次舍入丢失的低位下次加法时补回去。改完之后输出恢复正常。这件事让我对std::numeric_limitsfloat的几个成员有了更深的理解。epsilon()返回的是1.19e-7表示1和大于1的最小可表示float之间的差值。digits106意味着把任意float转成十进制再转回来6位以内是可靠的。这些信息光看文档是记不住的只有在真实环境中踩过坑才能刻进脑子里。整个过程都在阿贝云的免费云服务器上完成。SSH连接很顺畅延迟在30ms左右。编译速度比我想象中快虽然只有1核1G的配置但SSD硬盘的IO确实给力。中途还试着重装了一次系统从CentOS切到Ubuntu控制面板上一键重装也就几分钟的事。对于这种学习性质的C开发完全够用了。免费虚拟主机和免费云服务器这两种产品我觉得云服务器更适合做这种需要自己编译调试的开发工作自由度更高。以后写涉及浮点数比较的代码应该会先查一下std::numeric_limitsT::epsilon()再决定误差容忍范围而不是拍脑袋写个if (abs(a-b) 1e-6)。毕竟不同精度类型、不同数值量级下的合理误差阈值完全不同。阿贝云 https://www.abeiyun.com 的免费资源虽然配置不算高但对学习阶段来说足够了。不用自己花钱买云服务就能有一台独立IP的Linux环境随便折腾对学生党确实友好。这台服务器上还跑过几个小型的Web项目练手5M带宽够用。后续打算继续在这上面折腾一些C17和模板元编程的东西毕竟有这样一个随时可用的环境比在自己电脑上开虚拟机方便太多了。本文包含AI生成内容
返回列表