浮点运算有时候看起来有些麻烦和神秘。在C语言中这个问题尤其严重一些,因为C语言传统上并不是用来设计大量使用浮点数的程序的。大多数电脑都是用二进制来表示浮点数和整数的。例如,在十进制里,0.1是个简单、精确的小数,但是用二进制表示起来却是个循环小数0.0001100110011...。在对一些二进制中无法精确表示的小数进行赋值后再读取输出时,也就是从十进制转换成二进制再转回十进制时,你会观察到数值的不一致。
计算机一般都是用一种浮点的格式来近似地模拟实数算术运算,注意是近似,而不是完全精确。计算机浮点运算的结合率和分配率并不是一定完全成立。也就是说,运算顺序可能会影响结果,而连加也不一定和乘法等价。下溢、误差的累积和其他非常规性是常见的麻烦。
不要假设浮点运算结果是精确的,尤其不能直接比较两个浮点数是否相等。
浮点数的定义决定它的绝对精度会随着其量级而变化,所以比较两个浮点数的最好方法就要利用一个与浮点数的量级相关的精确阈值。不要用下面这样的代码:
double a,b;
...
if(a==b)
要用类似这样的方法:
#include <math.h>
if(fabs(a-b)<=epsilon*fabs(a))
epsilon(表示小的正数)被赋予一个特定的值来控制“接近度”。选择epsilon的值也要小心:它的合适值可能很小、只与机器的浮点精度有关;如果被比较的值本来精度就不高,或者是连续运算、误差累积的结果,这个值也可以定得稍大。同时也要确保a不等于0。
用绝对阈值的方法肯定要差些,通常也不推荐:
比如下面的代码页也是不好的:
if(fabs(a-b)<0.001)
因为0.001这样的绝对“模糊因子”恐怕难以持续有效。随着被比较的数不断变化,很可能两个较小的本应看作不相等的数正好相差小于0.001,而两个本应看作相等的两个大数却相差大于0.001(这是因为浮点数的定义决定它的绝对精度会随着其量级而变化)。显然,将模糊因子修改为0.005或者0.0001或其他任何绝对数都无助于解决这个问题。
推荐使用下面的“相对差”函数。它返回两个实数的相对差值,如果两个数完全相同等于0,则会返回0.0,否则返回差值和较大数的比值:
#define Abs(x)((x)<0?-(x):(x))
#define Max(a,b) ((a)>(b)?(a):(b))
double RelDif(double a,doulbe b)
{
double c = Abs(a);
double d = Abs(b);
d = Max(c,d);
return d == 0.0?0.0:Abs(a-b)/d;
}
典型的用法是:
if(RelDif(a,b)<=TOLERANCE)
...
浮点数取整最简单、直接的方法是使用这样的代码:(int)(x+0.5)
C语言的浮点数到整数的转换会去掉小数部分,因此在取整之前加上0.5会使>=0.5的小数部分进位。但是这个方法对于负数并没有效。这是一个改进的方法:
(int)(x<0?x-0.5:x+0.5)
要保留特定的精度,可以使用:
(int)(x/precision + 0.5)*precision
计算机一般都是用一种浮点的格式来近似地模拟实数算术运算,注意是近似,而不是完全精确。计算机浮点运算的结合率和分配率并不是一定完全成立。也就是说,运算顺序可能会影响结果,而连加也不一定和乘法等价。下溢、误差的累积和其他非常规性是常见的麻烦。
不要假设浮点运算结果是精确的,尤其不能直接比较两个浮点数是否相等。
浮点数的定义决定它的绝对精度会随着其量级而变化,所以比较两个浮点数的最好方法就要利用一个与浮点数的量级相关的精确阈值。不要用下面这样的代码:
double a,b;
...
if(a==b)
要用类似这样的方法:
#include <math.h>
if(fabs(a-b)<=epsilon*fabs(a))
epsilon(表示小的正数)被赋予一个特定的值来控制“接近度”。选择epsilon的值也要小心:它的合适值可能很小、只与机器的浮点精度有关;如果被比较的值本来精度就不高,或者是连续运算、误差累积的结果,这个值也可以定得稍大。同时也要确保a不等于0。
用绝对阈值的方法肯定要差些,通常也不推荐:
比如下面的代码页也是不好的:
if(fabs(a-b)<0.001)
因为0.001这样的绝对“模糊因子”恐怕难以持续有效。随着被比较的数不断变化,很可能两个较小的本应看作不相等的数正好相差小于0.001,而两个本应看作相等的两个大数却相差大于0.001(这是因为浮点数的定义决定它的绝对精度会随着其量级而变化)。显然,将模糊因子修改为0.005或者0.0001或其他任何绝对数都无助于解决这个问题。
推荐使用下面的“相对差”函数。它返回两个实数的相对差值,如果两个数完全相同等于0,则会返回0.0,否则返回差值和较大数的比值:
#define Abs(x)((x)<0?-(x):(x))
#define Max(a,b) ((a)>(b)?(a):(b))
double RelDif(double a,doulbe b)
{
double c = Abs(a);
double d = Abs(b);
d = Max(c,d);
return d == 0.0?0.0:Abs(a-b)/d;
}
典型的用法是:
if(RelDif(a,b)<=TOLERANCE)
...
浮点数取整最简单、直接的方法是使用这样的代码:(int)(x+0.5)
C语言的浮点数到整数的转换会去掉小数部分,因此在取整之前加上0.5会使>=0.5的小数部分进位。但是这个方法对于负数并没有效。这是一个改进的方法:
(int)(x<0?x-0.5:x+0.5)
要保留特定的精度,可以使用:
(int)(x/precision + 0.5)*precision