打印
[技术讨论]

【C语言实战经验4】浮点数运算,你踩过什么坑?

[复制链接]
23|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
dffzh|  楼主 | 2025-5-19 11:17 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
#申请原创#
@21小跑堂

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

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


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


使用标准C库函数isnormal:


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


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

如果你有更多关于C语言浮点数运算方面的踩雷经历和避坑技巧,欢迎来贴分享!
让我们一起进步,变的更加优秀。

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

40

主题

388

帖子

5

粉丝