5

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

 3 years ago
source link: https://blog.popkx.com/c%E8%AF%AD%E8%A8%80%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E4%B8%AD-%E5%A4%84%E7%90%86%E6%B5%AE%E7%82%B9%E6%95%B0%E6%97%B6%E5%BA%94%E6%B3%A8%E6%84%8F%E5%93%AA%E4%BA%9B/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK