C语言程序开发中,处理浮点数时应注意哪些?

在C语言程序开发中,有时浮点运算是不可避免的,遗憾的是,由于计算机的精度是有限的,所以很多时候C语言程序并不总是能够精确的存储和处理浮点数,这也是很多C语言程序尽力使用整型运算代替浮点运算的原因之一。

计算机存储浮点数的方式与存储整数的方式有所不同,遇到很长的浮点数时,总不能采用无限制长度的空间存储吧?事实上,对于计算机中浮点数的存储,国际上制定过一些标准。这一点可参考我之前的文章《》。

鉴于C语言程序不能总是精确的表示浮点数,在我们编写程序时,需要谨记这一特点,否则最终C语言程序可能会产生难以理解的结果。本节将以问答的形式讨论C语言程序开发中,涉及的浮点运算基本注意事项。

定义 float 变量赋值为 3.1,为什么使用 printf 打印出来的是 3.0999999?

其实并不总是如此。很多计算机使用二进制表示整数,也使用二进制表示浮点数。我们人类常用十进制表示数字,遗憾的是,十进制能够精确表示的数字,并不一定也能使用二进制表示精确。

例如在十进制中,分数 1/3 = 0.33333...无数个3,十进制是无法精确表示这一数字的。同样的道理,0.1 也即 1/10 在二进制中也是无法精确表示的(0.0001100110011...),所以在这种情况下,3.1 也就无法精确表示了,而只能使用 3.0999999 去近似。

使用 printf() 函数打印 3.1,最终显示到终端的结果主要取决于所使用计算机中二/十进制的转换仔细程度。实际上,有时我们将一个精确的十进制浮点数转换为二进制,然后再转换回十进制,会发现前后两个数字并不“相等”。

为什么 sqrt(144.) 得不到正确结果?

小明希望使用C语言math库的 sqrt() 函数计算 144 的平方根,于是他写出下面这样的C语言代码,请看:

#include <stdio.h>
int main()
{
    printf("%f\n", sqrt(144.));
    return 0;
}

但是小明编译这段C语言代码并执行后,发现程序输出的是一串杂数,这是怎么回事呢?

仔细观察小明的C语言代码,能够发现他虽然调用了 math 库中的函数,但是却并没有包含相应的头文件“math.h”,这造成的后果是C语言编译器无法确定 sqrt() 函数的原型。此时,编译器只好“猜测”sqrt() 的函数原型为:

int sqrt();

也即编译器认为 sqrt() 函数的返回值为一个整数。如果读者看了我之前的文章,应该明白计算机存储整数和浮点数的方式是不同的,因此C语言程序以浮点数的存储方式解释整数时,自然是有可能出现不预期的结果的。

如果C语言编译器处理函数时未发现其原型,一般都会将 int f(); 当作该函数原型。

如何判断两个浮点数“相等”?

计算机无法精确表示浮点数,这个特性也影响了C语言程序判断两个浮点数是否相等,如果读者看过我之前的文章,应该清楚在C语言程序中,== 运算符是不能用于判断两个浮点数是否相等的。

double  a, b;
...
if(a==b)    /** 错误 */
    ...

事实上,鉴于C语言程序无法总是准确表示浮点数,在需要判断两个浮点数是否“相等”时,通常的做法是判断这两个数是否足够“接近”:

#include <math.h>
if(fabs(a-b) <= epsilon * fabs(a))

其中 epsilon 是一个非常小的整数,上述表达式将浮点数 a,b足够接近近似为“相等”。虽说 epsilon 应该在浮点数表示范围内足够小,但是实际开发中,只要能够满足实际需要的精度就可以了,“够用就行”。因此,epsilon 也可以是程序员自行设定的阈值。

如何赋值一些特殊值,如 NaN(not a number,不是数字)?

许多平台都提供了方便处理浮点数值的工具或者函数,比如一些预定义的常数,以及类似 isnan() 的函数,这些工具要么作为 <math.h> 中的标准扩展,要么作为 <ieee.h> 或 <nan.h> 的非标准扩展。

一般来说,如果某个数字不与自己相等,那么这个数必定是 NaN:
#define  isnan(x)    ((x) != (x))

不过,应该注意的是,为遵守 IEEE 的C语言编译器可能会对这样的对比代码优化。还要注意,即使系统中对 NaN 这样的特殊数字做了预定义,我们也不能将其与其他数字对比,例如 if(x==NaN) 就是不合适的代码,因为 NaN 不能与数字做比较。

如果开发C语言程序的平台没有直接提供类似于 NaN 这样的特殊数字,我们可以自行定义它们,例如:

double nan = 0./0.;
double inf = 1./0.

不过应该明白的是,有些编译器会将上述两行C语言代码当作“浮点异常”处理,此时我们将不能得到 nan 和 inf。

对于 NaN 和 inf,还有一点需要清楚的是 sprintf() 函数的格式输出,在有些平台,它能产生类似于“NaN”以及“inf”的字符串。

小结

本节主要介绍了C语言程序开发中,处理浮点数时应注意的事项。因为计算机无法总是精确存储浮点数,所以处理浮点数和处理整数是有所区别的,如果不清楚这些,很容易写出有问题的代码。这里强烈推荐大家阅读我之前的文章:《》。

阅读更多:   C语言
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif