[深入浅出C语言]三大零值及浮点数比较问题

881 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

前言

        关于C语言中的三大零值不知道大家了解多少呢,还有浮点数比较问题,浮点数能够直接用比较运算符进行比较吗?为什么呢?

        本文就来分享一波作者的学习心得与见解。

        笔者水平有限,难免存在纰漏,欢迎指正交流。

三大零值

0
\0
NULL

        它们的值都是0,但是有区别:类型的区别

        0是int类型

        转到定义可以查看到:NULL 是((void *)0),也就是指针类型

        '\0'是char类型

        然而类型的区别只是给使用者和编译器来区分的,实际上值都是0,对于计算机没什么区别。

C语言有没有bool类型

        c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。

        但是c99引入了_Bool类型(你没有看错,_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了bool,为了保证C/C++兼容性)。

        实际上通常使用C89和C90,也就是bool类型用的比较少,直接用伪bool类型(一般是int)由非零为真零为假来判断,而且一般这样:if(flag),直接用变量值去和零比较。

        实际上,如果你使用的是VS的话,会发现还有一个BOOL类型,对应TRUE和FALSE,为什么会这样呢?这是微软设计的只适合微软工具的类型,不具备可移植性,不推荐使用。

指针变量与“零值”进行比较

int *p = NULL;
//(1)
if(p == 0)
//(2)
if(p)
//(3)
if(NULL == p)

其实都可以,但是哪个比较好呢?

(1)写法可能引起误会,有可能把p认为是整型变量或其他类型

(2)写法也同(1),可能会认为是“bool”类型

(3)写法更为稳妥些,毕竟看到NULL立马就能认出p应该是指针类型

浮点数的比较问题

        首先我们要明确一个点:浮点数存储时并不是完整存储的,是有可能产生精度损失的,这与浮点数在内存中的存储方式有关,以前的文章已经讲过了,这里不再赘述。文章链接:[深入浅出C语言]浅析浮点数 - 掘金

        注意这里的损失并不是一味的减少小,还有可能增大。浮点数本身存储的时候若遇到计算不尽的情况会“四舍五入”。

浮点数和0.0比较

#include <stdio.h>

int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50f\n", x - 0.9);
    printf("%.50f\n", y);
    
    if ((x - 0.9) == y)
    {
    	printf("you can see me!\n");
    }
    else
    {
    	printf("oops\n");
    }
    return 0;
}

        很显然,浮点数的存和取并不那么准确。

        可能有人就会说:把%.50f改成%.1f是不是就没问题了,毕竟精度精确到了小数点后一位嘛,后面的全部四舍五入了呗。

那我们试试看:

int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.1f\n", x - 0.9);
    printf("%.1f\n", y);
    
    if ((x - 0.9) == y) 
    {
    	printf("you can see me!\n");
    }
    else 
    {
    	printf("oops\n");
    }
    return 0;
}

        这样一来好像打印没什么问题了,但是还是不能用==比较欸。其实上面修改的精度是printf()函数打印浮点数的精度,实际上浮点数存储时很可能存在精度损失,这是难以避免的,有可能比理想值高一点或低一点,无法满足==运算的精确要求。

结论:因为精度损失问题,两个浮点数,绝对不能使用==进行相等比较。

两个浮点数该如何比较

        应该进行范围精度比较,只要计算结果符合给定的精度范围要求,我们在误差允许范围内就认为该比较结果可靠。

对于==比较,需要两数之差在给定的精度范围内,可以有两种写法:

//伪代码
if((x-y) > -精度 && (x-y) < 精度)
{
	//TODO
}
//伪代码-简洁版
if(fabs(x-y) < 精度)//fabs是浮点数求绝对值
{ 
	//TODO
}

对于>比较,需要两数之差大于精度值:

//伪代码
if(x-y > 精度)
{
	//TODO
}

对于<比较,需要两数之差小于精度值:

//伪代码
if(x-y < 精度)
{
	//TODO
}

精度:

        如果有需要,可以试试自己设置,通常是宏定义。

        一般推荐使用系统精度:DBL_EPSILON和FLT_EPSILON

#include<float.h> //使用下面两个精度,需要包含该头文件
DBL_EPSILON //double 最小精度
FLT_EPSILON //float 最小精度
//两个精度定义
#define DBL_EPSILON 2.2204460492503131e-016

#define FLT_EPSILON 1.192092896e-07F 

        XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。

        EPSILON这个单词翻译过来是'ε'的意思,数学上,就是一个极小的正数 。

代码调整后

#include <stdio.h>
#include <math.h> //必须包含math.h,要不然无法使用fabs
#include <float.h> //必须包含,要不然无法使用系统精度

int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50f\n", x - 0.9);
    printf("%.50f\n", y);
    
    if (fabs((x - 0.9) - y) < DBL_EPSILON)//原始数据是双精度浮点数,我们就用DBL_EPSILON
    { 
        printf("you can see me!\n");
    }
    else
    {
    	printf("oops\n");
    }
    
    return 0;
}

和0.0比较

int main()
{
    double x = 0.00000000000000000000001;
    //有三种写法:
    if (fabs(x-0.0) < DBL_EPSILON)//写法1
    { 
    	printf("you can see me!\n");
    }
    if (fabs(x) < DBL_EPSILON)//写法2
    { 
    	printf("you can see me!\n");
    }
    if(x > -DBL_EPSILON && x < DBL_EPSILON)//写法3
    { 
    	printf("you can see me!\n");
    }
    else
    {
    	printf("oops\n");
    }

    return 0;
}

可不可以出现等号

        x > -DBL_EPSILON && x < DBL_EPSILON: 为何不是>= && <= 呢?

个人看法:

        XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。

        XXX_EPSILON+n不等于n的最小的正数: 有很多数字+n都可以不等于n,但是XXX_EPSILON是最小的,而XXX_EPSILON依旧是引起不等的一员。

        换句话说:fabs(x) <= DBL_EPSILON(确认x是否是0.0的逻辑),要是相等了,就说明x本身就相当于最小误差DBL_EPSILON了,已经能够引起其他与它相加减的数据本身的变化了,可是这个不符合0.0不能引起其他数据变化的原则,就矛盾了,所以建议是不要加上等号,只要在精度范围内就可以说明是0.0了(小于了最小误差,就认为是0.0,实际上值不是真的0.0)。


以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~

src=http___c-ssl.duitang.com_uploads_item_201708_07_20170807082850_kGsQF.thumb.400_0.gif&refer=http___c-ssl.duitang.gif