浮点数的比较

2022/03/22

在学习浮点数(float)的时候很多书上面都会以0.1+0.2 ≠ 0.3为例来说明不要直接用==来比较两个浮点数是否相等因为很容易出现一些意想不到的问题,那么为什么会出现这种现象呢?浮点数应该如何比较才不会出现错误呢?这里将以C++语言为例,来进行分析并解决这些问题。

1. 错误复现

首先通过下面这段简单的程序看能否复现上面的\(0.1 + 0.2 \neq 0.3\)

void test1(){
    if(0.1 + 0.2 == 0.3){
        cout << "yes" << endl;
    }else{
        cout << "no" << endl;
    }    
}

输出结果为no,和设想的一样出现了错误。再看下面这段程序

void test2(){
    float a = 0.1;
    float b = 0.2;
    float c = 0.3;
    if(a + b == c){
        cout << "yes" << endl;
    }else{
        cout << "no" << endl;
    }
}

输出结果为yes, 也就是说这里0.1 + 0.2 = 0.3和上面得出的结果并不相同,再看最后一个例子

void test3(){
    double a = 0.1;
    double b = 0.2;
    double c = 0.3;
    if(a + b == c){
        cout << "yes" << endl;
    }else{
        cout << "no" << endl;
    }
}

输出的结果为no, 和第一个的输出结果是相同的。上面三个例子只有第二个计算计算结果是正确的,第一个和第二个都得出了\(0.1 + 0.2 \neq 0.3\)的错误结果,在第二个与第三个例子中只有小数的表示类型不同前者为float而后者为double, 所以计算结果可能与表示类型有关,在第一个例子中三个小数都没有提前进行定义类型,那么它们的表示类型是什么呢?

2. 未定义数的表示类型

C++可以通过typeid()来获取运行中变量的数据类型,使用下面这行代码就可以获取直接使用0.1的数据类型了

cout << typeid(0.3).name() << endl;

输出结果为d表示是double类型,所以当使用float类型的时候0.1 + 0.2 == 0.3返回的为true,而使用double类型的时候返回的是false,下面从bit层面看一下在怎样造成这种结果的

3. 从bit层面来解释错误的发生

可以通过下面这段代码来获取0.10.20.3已经0.1+0.2的十六进制表示

typedef unsigned char* byte_pointer;

void show_bytes(byte_pointer start, size_t len){
    size_t i;
    for(int i=len-1; i>=0; i--){
        printf(" %.2x", start[i]);
    }
    printf("\n");
}


int main(){
    double alpha = 0.1;   
    double beta = 0.2;
    double gamma = alpha + beta;
    double delta = 0.3;
    show_bytes((byte_pointer)& alpha, sizeof(double));
    show_bytes((byte_pointer)& beta, sizeof(double));
    show_bytes((byte_pointer)& gamma, sizeof(double));
    show_bytes((byte_pointer)& delta, sizeof(double));
}

输出的结果为

 3f b9 99 99 99 99 99 9a
 3f c9 99 99 99 99 99 9a
 3f d3 33 33 33 33 33 34
 3f d3 33 33 33 33 33 33

因为0.10.2还有0.3都不能准确的通过二进制表示出来,所以都是取的约数,最后0.1的约数与0.2的约数相加获得的约数与0.3的约数自然可能会不同。那为什么float会相同呢,这不过是一个巧合罢了,在这个例子中它是相同的,再换一个相似的例子可能就不同了。所以最好不用对两个浮点数直接进行比较,这样的错误很难找出。

4. 如何正确的比较浮点数呢

在一定的精度之内比较两个浮点数的大小基本上都可以得出正确的值,但是如何比较两个浮点数是否相等呢?可以直接对两个值做差,然后选择一个精度并判断前面的插值是否大于那个精度,如果大于则说明不相等,然后不大于则说明两个浮点数是相等的,如下面这个例子

if(fabs(0.1+0.2 - 0.3) < ESPIPE){
        cout << "yes" << endl;
    }else{
        cout << "no" << endl;
}

其中ESPIPE是一个非常小的数字,当然你可以自己指定精度,比如1e-6,返回结果为yes所以说明他们是相等的。