编写安全代码:小心使用浮点数
浏览:2829次 出处信息
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
其实浮点数使用时的注意事项,是一个老生常谈的问题,我本来不想写这个东西的。但是论坛上偶尔还是会有一些朋友问一些浮点的问题,另外前几天写得一篇博文《有趣的问题:C的表达式x==x何时为假》http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=3126229&cid=565775&comment=true
在那篇文章中,通过x != x为假这一有趣的问题,引出一个特殊的浮点数值NaN。当时我还为了对比浮点与整数的转换,所以使用了memcpy来达到用0xff填充浮点数,来形成NaN。但是在评论中,发现有的朋友反而因为示例中的memcpy而没有注意到重点NaN。
那么今天就重点说一下NaN,并顺带说一下浮点的其它陷阱。
1. 浮点的精度限制。
浮点数的存储格式与整数完全不同。大部分的实现采用的是IEEE 754标准,float类型,是1个sign bit,8 exponent bits,23 mantissa bits。而double类型,是1个sign bit,11 exponent bits,52 mantissa bits。至于浮点如何去表示小数,请自行搜索google。由于float使用的小数表示方法,导致浮点数值是有精度限制的。
有限的精度就引发了浮点数值使用时的两个陷阱。
1)交换定律不适用浮点数
如有三个浮点数float x=1/3,y=1/6,z=1/7,而x*y/z不等于x*(y/z)
- #include <stdlib.h>
- #include <stdio.h>
- int main(void)
- {
- float x = 1/3;
- float y = 1/6;
- float z = 1/7;
- if (x*y/z != x*(y/z)) {
- printf("Not equal!\n");
- }
- return 0;
- }
编译输出:
- [fgao@fgao-vm-fc13 test]$ gcc -g test.c
- [fgao@fgao-vm-fc13 test]$ ./a.out
- Not equal!
而对于整数来说,如果不发生溢出的情况下,x*y/z是等于x*(y/z)。
2)浮点数的比较要使用范围比较
如:
- #include <stdlib.h>
- #include <stdio.h>
- int main(void)
- {
- float x = 0.123-0.11-0.013;
- if (x == 0) {
- printf("x is 0!\n");
- }
- if (-0.0000000001 < x && x < 0.0000000001) {
- printf("x is in 0 range!\n");
- }
- return 0;
- }
编译输出:
- [fgao@fgao-vm-fc13 test]$ gcc -g test.c
- [fgao@fgao-vm-fc13 test]$ ./a.out
- x is in 0 range!
这两个都是比较常见的浮点陷阱,下面要说明的是浮点数值的两个exception
1)infinite无限
2)NaN即Not a Number
其中NaN为最为特殊的一个“浮点值”——它不是一个合法的浮点值
在前面的文章中,我使用memcpy构造了一个非法的浮点数值,它导致了x与自身比较的失败。那么,有的朋友会说平时谁会用memcpy去填充浮点啊,那么NaN就离我很远了啊。请看下面的例子:
- #include <stdlib.h>
- #include <stdio.h>
- int main(void)
- {
- float x = 1/0.0;
- printf("x is %f\n", x);
- x = 0/0.0;
- printf("x is %f\n", x);
- return 0;
- }
编译输出
- [fgao@fgao-vm-fc13 test]$ gcc -g test.c
- [fgao@fgao-vm-fc13 test]$ ./a.out
- x is inf
- x is -nan
当1除以0.0时,我们得到的是infinite,而是用0除以0.0时,得到的就是NaN。这里完全是普通的除法运算,也会产生NaN的情况。
那么当使用除法的时候,对除数进行检查,保证其不为0.0是否就可以避免NaN了呢?再看下面的代码:
- #include <stdlib.h>
- #include <stdio.h>
- int main(void)
- {
- float x;
- while (1) {
- scanf("%f", &x);
- printf("x is %f\n", x);
- }
- return 0;
- }
编译执行
- [fgao@fgao-vm-fc13 test]$ ./a.out
- inf
- x is inf
- nan
- x is nan
示例代码中,调用scanf来得到用户输入的浮点数。令人惊讶的是,scanf作为C库函数是接受浮点数的这两种exceptions的,用户可以直接输入无限inf和NaN。而C库中究竟有多少种输入输出函数支持这两种exception,我也不知道。那么对于UI程序来说,当遇到浮点数值的时候,我们必须要判断该浮点数是否为一个合法的浮点数,要对用户输入值进行检查,或者说对于一切不属于本模块的浮点输入值都要进行检查。——我在我同事那就遇到一个开源库返回的浮点数为NaN,才引发的前文。
以上面的代码为例,应该为:
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
- int main()
- {
- float x;
- while (1) {
- scanf("%f", &x);
- if (isinf(x)) {
- printf("It's infinite\n");
- }
- if (isnan(x)) {
- printf("It's NaN\n");
- }
- printf("x is %f, 0x%X\n", x, *(int*)&x);
- }
- return 0;
- }
编译运行:
- inf
- It's infinite
- x is inf, 0x7F800000
- nan
- It's NaN
- x is nan, 0x7FC00000
其中isinf和isnan为C库提供的检测函数,分别用于检查infinite和NaN。而isnan实际上就是返回x != x,利用的就是NaN的特性,与任何数值进行相等比较都是返回false。所以当x != x时,即为NaN浮点值。
建议继续学习:
- [python]定制JSON中的浮点数格式 (阅读:3028)
- 浮点数的二进制表示 (阅读:2575)
- 关于PHP浮点数你应该知道的(All ‘bogus’ about the float in PHP) (阅读:1783)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
扫一扫订阅我的微信号:IT技术博客大学习
<< 前一篇:编写安全代码:再论整数类型转换
文章信息
- 作者:gfree.wind@gmail.com 来源: kernelchina blogs
- 标签: 浮点数
- 发布时间:2012-06-20 22:52:15
近3天十大热文
- [55] IOS安全–浅谈关于IOS加固的几种方法
- [53] android 开发入门
- [52] 如何拿下简短的域名
- [52] 图书馆的世界纪录
- [50] Oracle MTS模式下 进程地址与会话信
- [50] Go Reflect 性能
- [48] 【社会化设计】自我(self)部分――欢迎区
- [47] 读书笔记-壹百度:百度十年千倍的29条法则
- [36] 程序员技术练级攻略
- [27] 视觉调整-设计师 vs. 逻辑