IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

编写安全代码:小心使用浮点数

kernelchina blogs 2012-06-20 22:52:15 累计浏览 3,795 次
本机暂存
作者:gfree.wind@gmail.com
博客: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)
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5.     float x = 1/3;
  6.     float y = 1/6;
  7.     float z = 1/7;
  8.     if (x*y/z != x*(y/z)) {
  9.         printf("Not equal!\n");
  10.     }
  11.     return 0;
  12. }
编译输出:
  1. [fgao@fgao-vm-fc13 test]$ gcc -g test.c
  2. [fgao@fgao-vm-fc13 test]$ ./a.out
  3. Not equal!

而对于整数来说,如果不发生溢出的情况下,x*y/z是等于x*(y/z)。

2)浮点数的比较要使用范围比较
如:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5.     float x = 0.123-0.11-0.013;
  6.     if (x == 0) {
  7.         printf("x is 0!\n");
  8.     }
  9.     if (-0.0000000001 < x && x < 0.0000000001) {
  10.         printf("x is in 0 range!\n");
  11.     }
  12.     return 0;
  13. }

编译输出:

  1. [fgao@fgao-vm-fc13 test]$ gcc -g test.c
  2. [fgao@fgao-vm-fc13 test]$ ./a.out
  3. x is in 0 range!

这两个都是比较常见的浮点陷阱,下面要说明的是浮点数值的两个exception

1)infinite无限
2)NaN即Not a Number
其中NaN为最为特殊的一个“浮点值”——它不是一个合法的浮点值
在前面的文章中,我使用memcpy构造了一个非法的浮点数值,它导致了x与自身比较的失败。那么,有的朋友会说平时谁会用memcpy去填充浮点啊,那么NaN就离我很远了啊。请看下面的例子:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5.     float x = 1/0.0;
  6.     printf("x is %f\n", x);
  7.     x = 0/0.0;
  8.     printf("x is %f\n", x);
  9.     return 0;
  10. }

编译输出

  1. [fgao@fgao-vm-fc13 test]$ gcc -g test.c
  2. [fgao@fgao-vm-fc13 test]$ ./a.out
  3. x is inf
  4. x is -nan
当1除以0.0时,我们得到的是infinite,而是用0除以0.0时,得到的就是NaN。这里完全是普通的除法运算,也会产生NaN的情况。
那么当使用除法的时候,对除数进行检查,保证其不为0.0是否就可以避免NaN了呢?再看下面的代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5.     float x;
  6.     while (1) {
  7.         scanf("%f", &x);
  8.         printf("x is %f\n", x);
  9.     }
  10.     return 0;
  11. }

编译执行

  1. [fgao@fgao-vm-fc13 test]$ ./a.out
  2. inf
  3. x is inf
  4. nan
  5. x is nan

示例代码中,调用scanf来得到用户输入的浮点数。令人惊讶的是,scanf作为C库函数是接受浮点数的这两种exceptions的,用户可以直接输入无限inf和NaN。而C库中究竟有多少种输入输出函数支持这两种exception,我也不知道。那么对于UI程序来说,当遇到浮点数值的时候,我们必须要判断该浮点数是否为一个合法的浮点数,要对用户输入值进行检查,或者说对于一切不属于本模块的浮点输入值都要进行检查。——我在我同事那就遇到一个开源库返回的浮点数为NaN,才引发的前文。

以上面的代码为例,应该为:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <math.h>
  4. int main()
  5. {
  6.     float x;
  7.     while (1) {
  8.         scanf("%f", &x);
  9.         if (isinf(x)) {
  10.             printf("It's infinite\n");
  11.         }
  12.         if (isnan(x)) {
  13.             printf("It's NaN\n");
  14.         }
  15.         printf("x is %f, 0x%X\n", x, *(int*)&x);
  16.     }
  17.     return 0;
  18. }

编译运行:

  1. inf
  2. It's infinite
  3. x is inf, 0x7F800000
  4. nan
  5. It's NaN
  6. x is nan, 0x7FC00000

其中isinf和isnan为C库提供的检测函数,分别用于检查infinite和NaN。而isnan实际上就是返回x != x,利用的就是NaN的特性,与任何数值进行相等比较都是返回false。所以当x != x时,即为NaN浮点值。

同分类推荐文章

  1. 对基本有序的序列排序算法 (2026-06-11 17:46:49)
  2. Four Levels Of Customer Understanding (2026-05-22 21:00:00)
  3. 除法的意义 (2026-04-12 20:52:17)

查看更多 算法 文章 →

建议继续学习

  1. [python]定制JSON中的浮点数格式 (累计阅读 3,889)
  2. 浮点数的二进制表示 (累计阅读 3,568)
  3. 一道随机数题目的求解 (累计阅读 3,203)
  4. 用牛顿迭代法求整数的平方根 (累计阅读 3,199)
  5. 关于PHP浮点数你应该知道的(All ‘bogus’ about the float in PHP) (累计阅读 2,774)
  6. 每一个JavaScript开发者应该了解的浮点知识 (累计阅读 2,163)
  7. 如果对Heron公式求导的话 (累计阅读 1,545)