news 2026/2/26 3:22:18

[技术讨论] 【C语言实战经验4】浮点数运算,你踩过什么坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[技术讨论] 【C语言实战经验4】浮点数运算,你踩过什么坑
在C语言的浮点数运算(包括float和double两种浮点数据类型)方面,你踩过什么坑?
有没有朋友曾经遇到过或解决过因浮点数运算操作不当引起的Bug?在解决的时候,是否还很疑惑为什么不能这样操作浮点数?
前几年参加软件培训时听过的一个因浮点数的特殊性造成的军事问题,与大家分享:
1991年2月25日,在海湾战争的时候,美国爱国者导弹由于拦截伊拉克飞毛腿导弹失败,导致美国一个兵营近28人死亡。
那为什么会拦截失败呢?原因是这样的:
爱国者导弹的软件系统里内置了一个计数时钟,每隔0.1秒计数加1,为了以秒为单位来确认时间,软件代码里用一个24位的近似于1/10的二进制小数值与计数器值相乘。我们知道,计算机中的1/10的二进制其实是一个以0011为无限循环的无穷序列:0.000110011[0011]……,而计算机中仅用序列的开头位和二进制小数点右边的23位来近似表示0.1,那么其与0.1之间的差值就是:0.000000000000000000000001100[1100]……,即2-20*1/10 = 9.54*10-8秒;因此,如果从系统启动开始计算,时钟从0开始,且一直保持计数,如果按系统运行100小时后计算,实际时间就会相差0.0343秒,按导弹速度2000米/秒计算,那距离就相差68.7米。所以,因为距离偏差就导致了拦截失败。
上面这个新闻可能是真实的也可能是虚构的,但这不是我们关注的重点;
我们关注的重点是出现以上严重军事问题的原因是浮点数的精度问题。
我们再来看看浮点数0.1的输出打印情况:
备注:以下所有代码测试都基于64位系统,并在Visual C++ 6.0软件上实现。

为什么输出打印结果是0.100000,主要还是因为浮点数的二进制表示存在精度问题。
浮点数在计算机中是基于二进制存储的,而大多数十进制小数无法被二进制准确表示,因此会存在精度损失。例如,0.1在二进制中是一个无限不循环的小数,即0.1 (十进制) = 0.00011001100110011… (二进制)。在float类型中,只能存储23位小数部分,因此0.1会被截断并转换成一个近似值。
在打印浮点数时,打印函数会根据标准进行舍入和近似处理,尽可能使近似值接近原值,称为“最良近似值”‌1。因此,尽管0.1的精确二进制表示无法完全存储在float中,但在打印时通常会展示一个接近原值的近似值,如0.100000。
所以,0.1无法被精确存储,实际可能是0.100000或0.100000001或其他值。

另外,这种精度问题不仅限于0.1,其他小数如0.2和0.3在进行运算时也会因为二进制表示的限制而出现精度损失,即0.3-0.2的结果在打印时并不是0.1:

另外,在程序代码中也尽量不要直接进行浮点数的相等或不相等比较,这样可能会导致不精确的结果;
可以定义误差范围(阈值),并使用绝对误差的代码方式进行比较:


类似地,判断一个浮点数是否为零的时候,可以按如下两个方法进行
使用近似比较:


使用标准C库函数isnormal:


备注
对于C99及更高版本,可以使用isnormal函数来判断一个浮点数是否为非零且在标准范围内(即不是无穷大、NaN或零)。
此外,再说明一个在类型转换运算时需要注意的地方,直接展示代码执行结果:


从以上代码及其执行结果可以看出,浮点数在执行类型转换时是需要显示转换的,或者可以使用浮点数常量(如1.0),以避免在代码量多时就不容易找出问题所在的情况。
因此可以得出以下结论:
并非所有的浮点数都能够在计算机中使用二进制来完全表示;
计算机中对浮点数执行运算时,很有可能会产生舍入和截断;
并非所有的浮点比较都会出现错误,但是参与运算的浮点数就很容易出现误差,甚至极小的误差在被长时间放大后,由于累积误差,也会带来意想不到的严重后果。
所以,在C语言中操作浮点数,一定要记住:
不要直接对浮点数进行相等或者不相等的比较;
在判断浮点数是否为零时,需要通过类似于浮点数<=阈值来判断,阈值大小视情况而定;
循环控制表达式不应该包含有浮点数类型;
如果程序代码涉及浮点数,你一定要格外小心再小心;
使用浮点数来存储小数是不能得到精确值的,如果有高精度要求,可以考虑使用定点数或者高精度库(比如高精度浮点运算库GMP)。
如果你有更多关于C语言浮点数运算方面的踩雷经历和避坑技巧,欢迎来贴分享!
让我们一起进步,变的更加优秀。


---------------------
作者:dffzh
链接:https://bbs.21ic.com/icview-3454552-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/16 1:35:20

年末如何挑选手机?性能与影像成关键考量

年末购物季来临之际&#xff0c;消费者挑选一款中意手机时&#xff0c;常常面对众多眼花缭乱的选项。当下手机市场展现出技术多元、功能垂直之特性&#xff0c;不同产品线针对特定用户需求做了深度优化。进行选择时&#xff0c;全面综合考量性能、影像、显示、续航以及创新交互…

作者头像 李华
网站建设 2026/2/25 20:55:58

解决wslregisterdistribution failed错误的新方法:使用预构建镜像

解决 wslregisterdistribution failed 错误的新方法&#xff1a;使用预构建镜像 在深度学习项目开发中&#xff0c;一个稳定、即用的环境往往比模型本身更早成为“拦路虎”。尤其是在 Windows 平台上使用 WSL&#xff08;Windows Subsystem for Linux&#xff09;部署 PyTorch …

作者头像 李华
网站建设 2026/2/14 13:20:18

Jupyter Notebook保存检查点功能在PyTorch训练中的应用

Jupyter Notebook保存检查点功能在PyTorch训练中的应用 在深度学习项目中&#xff0c;最令人沮丧的场景莫过于&#xff1a;模型已经训练了十几个小时&#xff0c;结果因为一次意外断电、内核崩溃或不小心关掉了浏览器标签页&#xff0c;所有进度瞬间归零。这种“从头再来”的代…

作者头像 李华
网站建设 2026/2/17 4:48:43

【CMake】`add_subdirectory()` 命令详解

add_subdirectory() 是 CMake 中用于组织大型项目、模块化构建的核心命令&#xff0c;它允许将项目分解为多个子目录&#xff0c;每个子目录有自己的 CMakeLists.txt 文件。 基本语法 add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])参数详解 必需参数 source_d…

作者头像 李华