笔试面试的过程中难免会遇到很多自己不清楚或者是当时不清楚的问题,关于浮点型数据的问题我认为是比较难的问题啦,如果不清楚浮点型存储问题,回答问题只有猜结果了。
在面试的过程中一般面试官也很少问关于浮点数的问题,因为浮点数对于很多的处理器是不支持的,对于上层应用的程序员而言,存储类型的问题都会考虑,但是对于嵌入式程序员掌握内存的分配情况是必须的,只有这样才能把握基本的操作,关于浮点型数据,在C语言中存在两种类型的浮点型,其中一种是单精度的浮点型(float)、双精度的浮点型(double)。浮点型数据和一般的整形数据具有较大的差别,具体的差别就是浮点型数据是一个近似值,并不是一个准确的值,这也就是为什么在比较浮点型数据的大小时不能采用直接等于的方式进行比较,因为浮点型只是一个近似值,也就是一个值可能表示一个范围区间,这样的表达方式就使得对浮点型采用相等的判断方法进行判断可能不合理,只有通过比较一个数是否在这个小的范围内,在这个小的范围内说明这个数可以表述这个范围内的数据。因此在计算值比较两个浮点数变量不能通过做差是否等于零来判断。而只能通过如下的方式判断:
- const float ESPSION = 0.000001;
- if((x-y)>=-ESPSION && (x-y)<= ESPSION)
这种实现方式是基本的比较方式,这种判读方法刚好就是判断变量是否处于一个范围内,这里的范围是-0.000001<x<0.000001。
记得在网上关于浮点型的一道题目如下:
- typedef union test
- {
- float a;
- int i;
- char c[4];
- }Test;
- int main()
- {
- Test t;
- t.a = 5.0;
- printf("%f\n",t.a)
- printf("%d\n",t.i);
- printf("%c,%c,%c,%c\n",t.c[3],t.c[2],t.c[1],t.c[0]);
- return 0;
- }
这个题刚开始看的时候我并不知道其中有什么问题,我认为就是简单的数值强制类型转换,但是后面我发现并不是强制类型转换,这个题只有搞清楚了数据的存储方式才能真正的清楚输出的结果。
其实关于数据类型的存储方式问题的题目类型很多,其中比较常见的有:1、存储空间大小问题,即字节对齐问题,这种问题一般要求我们对基本类型了解其中的基本原理,相对来说比较简单。2、字符串的长度问题,这种问题主要是通过sizeof,strlen这两个不同的函数来比较。3、还有一类问题就是采用printf函数实现数据类型的转换问题。这种转化问题是在printf的格式中体现出来的,是各种关于内存分布最容易失误的地方,为什么说最容易失误呢,因为不同的参数就有可能产生不一样的输出结果,说白了这种题很容易出错是因为我们很少注意这些问题。
上面的这个题返回值非常的特殊,为什么会特殊呢?我就通过浮点数的基本存储方式来说明。在32位系统中,float类型占有4个bytes,double则占有8个bytes。对于整形数据而言,我们很容易就知道数据是顺序存储的,虽然有大小端之分,但是基本的准则我们认为是按照顺序存储的。但是float和double型却是比较特殊的数据类型,因为这两种数据类型并不是按照整形数据的顺序存储方式进行存储的,而是按照一定的标志IEEE来实现的,都有各自的标志编码方式:
浮点型变量在计算机内存中占用4字节(Byte),即32-bit。遵循IEEE-754格式标准。一个浮点数由2部分组成:底数m 和 指数e。
±mantissa × 2^exponent
(注意,公式中的mantissa 和 exponent使用二进制表示)
(具体可参看深入理解计算机系统)
底数部分 使用2进制数来表示此浮点数的实际值。
指数部分 占用8-bit的二进制数,可表示数值范围为0-255。但是指数应可正可负,所以IEEE规定,此处算出的次方(即是来自内存存储的内容,存储指数)须减去127才是真正的指数(实际的指数,如12.5转换为二进制为:1100.100=1.100100*23, 3即为实际指数)。所以float的指数可从 -126到128.
底数部分实际是占用24-bit的一个值,由于其最高位始终为1,所以最高位省去不存储,在存储中只有23-bit。到目前为止,底数部分23位加上指数部分8位 使用31位。那么前面说过,float是占用4个字节即32-bit, 那么还有一位是干嘛用的呢? 还有一位,其实就是4字节中的最高位,用来指示浮点数的正负,当最高位是1时,为负数,最高位是0时,为正数。
也就是说我们可以认为float在小端CPU的编码方式应该是:
31<-------------------------------------------------0
S(1bit)| E(8bits)| M(23bits) |
即:
-----------------------------------------------------
ADDR0+3 ADDR0+2 ADDR0+1 ADDR0
SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
-----------------------------------------------------
S: 表示浮点数正负,1为负数,0为正数。
E: 指数加上127后的值的二进制数
M: 24-bit的底数(只存储23-bit)
需要注意,浮点数为0时,指数和底数都为0,但此前的公式不成立。因为2的0次方为1,所以,0是个特例。当然,这个特例也不用认为去干扰,编译器会自动去识别。
这样我们就可以知道前面这个题中输出的结果啦,从上面的分析可以知道一个float型的数的基本存储方式,按照上面的实现可以知道5.0的存储方式为如下的形式:
01000000101000000000000000000000
从上面的这个存储形式我们可以得到基本的整形数值时0x40A00000。进而也就知道了4个bytes中的数值大小。
这是充分利用了联合体的共享内存特性,我们改变程序如下所示:
- #include<stdio.h>
- typedef union test
- {
- float a;
- int i;
- char c[4];
- }Test;
- int main()
- {
- Test t;
- t.a = 5.0;
- printf("%f\n",t.a);
- printf("%d\n",t.i);
- printf("%c,%c,%c,%c\n",t.c[3],t.c[2],t.c[1],t.c[0]);
- t.i = 65;
- printf("%f\n",t.a);
- printf("%d\n",t.i);
- printf("%c,%c,%c,%c\n",t.c[3],t.c[2],t.c[1],t.c[0]);
- return 0;
- }
根据上面的分析,可以比较方便的计算出结果如下所示:
当然这只是我在X86系统中的输出,在大端系统中会是什么结果我不得而知,具体的要参看IEEE标准。
float类型的数据是比较复杂的问题,我将在后面认真研究,争取早日解决各种问题。