浮点数的比较
由于计算机中采用有限位的二进制编码,因此浮点数在计算机中的存储并不总是精确的。
例如在经过大量计算后,一个浮点型的数3.14在计算机中就可能存储成3.1400000000001,
也有可能存储成3.1399999999999,这种情况下会对比较操作带来极大的干扰(因为C/C++
中的操作是完全相同才能判定为true)。于是需要引入一个极小数eps来对这种误差进行修正。
1.等于运算符(=)
![](https://img-blog.csdnimg.cn/2019082819054830.png)
const double eps = le-8;
为了使比较更加方便,把比较操作写成宏定义的形式:
#define Equ(a,b) ((fabs((a)-(b)))< (eps))
.
正如上面的代码,将a和b相减,如果差的绝对值小于极小量eps,那么就返回true,加上如此多的括号也是为了防止宏定义可能带来的错误。注意:如果想要使用不等于,只需要在使用时的 Equ 前面加一个非运算符“!”即可(!Equ(a, b))。于是在程序中就可以使用 Equ 函数来对浮点数进行比较了:
#include <stdio.h>
#include <math.h>
const double eps = le-8;
#define Equ(a'b) ((fabs((a)-(b)))<(epsj)
int main () {
double db = 1.23; /
if(Equ(db,1.23)) {
printf("true’*);
} else{
printf("false");
}
return 0;
}
有读者可能会改写成 db = 1.23 的写法,发现同样输 true。其实像这种比较简单的比较情况下是可以忽视误差的(事实上,如果没有经过容易损失精度的计算,就不需要考虑误差,可以直接比较X但是当一个变量进行了误差较大的运算后,精度的损失就不可忽视了。例如下面的代码中,dbl和db2的精确值都应该是it,但是却输出了 false;而使用了 Equ函 数就会输出true。
#include <stdio.h>
#include <math.h>
int main () {
double dbl = 4*asin (sqrt (2.0) / 2);
double db2 = 3 * asin(sqrt(3.0) / 2);
if(dbl == db2) {
printf("true”);
} else {
printf("false”);
return 0;
}
显然,由于这种误差的影响,其他比较运算符也会出现差错,因此必须进行修正。
2.大于运算符(>)
类比下面 < .
3.小于运算符(<)
![](https://img-blog.csdnimg.cn/20190828193312977.png)
b-eps b b+eps
如果一个数a要小于b,那么就必须在误差eps的扰动范围之外小于b,因此只有小于b-eps的数才能判定为小午b (也即a减b小于-eps)。
#define Less(afb) {((a)-(b))<(-eps))
4. 大于等于算符(>=)
类比下面小于等于。
5. 小于等于运算符(<=)
![](https://img-blog.csdnimg.cn/20190828193939717.png)
b-eps b b+eps
小于等于运算符可以理解为小于运算符和等于运算符的结合,于是需要让一个数a在误差扰动范围内能够判定其为小于或者等于b,因此小于b+eps的数都应当判定为小于等于b (也即a减b小于eps)。
#define LessEqu(arb) (((a)-(b)) < (eps))
6.圆周率Π
圆周率Π不需要死记,因为由cos(Π) = -1可知 Π = arccos(-l)。因此只需要把 Π 写成常量 acos(-1.0)即可。
const double Pi=acos(-1.0);
把上面的所有核心部分汇总起来就是下面这些代码:
const double eps=le-8;
const double Pi=acos (-1,0);
#define Equ(a,b) ((fabs((a)-(b)))<(eps))
#define More(a'b) (((a)-(b))>(eps))
#define Less(azb) (((a)-(b))<(-eps))
#define MoreEqu (a,b) ( ( (a) - (b) ) > (-eps))
#define LessEqu(a,b) (((a)-(b))<(eps))
这些部分不必死记硬背,因为懂了上面的原理之后很容易推断出来。
最后需要指出几点:
- 由于精度问题,在经过大量运算后,可能一个变量中存储的 0 是个很小的负数,这时如果对其开根号sqrt,就会因不在定义域内而出错。同样的问题还出现在asin(x)当x存放+1、acos(x)当x存放-1时。这种情况需要用eps使变量保证在定义域内。
- 在某些由编译环境产生的原因下,本应为0.00的变量在输出时会成-0.00。这个问题是编译环境本身的bug,只能把结果存放到字符串中,然后与-0.00进行没较,如果比对成功,则加上eps来修正为0.00。