原文出处:https://zhuanlan.zhihu.com/p/84453627?from_voters_page=true
ok,这里先说明一下,假设是在32位的机器上,int是32位。而float使用的是IEEE 754标准的单精度浮点数格式也是占用32位。
这时候float和int都是占用32位,占用同样的空间,但float范围是更大的,那我们为啥还要int呢?为啥不节省空间,只用float?我们来一探究竟!他们在计算机的大脑里是如何记忆的?
1、int对32个坑是如何使用的?
(实名吐槽知乎,居然没有表格。。。)
int类型的使用方法,大多学过计算机的,应该都是非常清楚的。二进制存储即可。
例1:请写出165(10进制)使用32位int型存储在计算机中的形式。
10进制转换为2进制,我个人喜欢先转为16进制,再写成2进制。
如下:
那么165在32位int型中是这样存储的(中间的0,我省略了):
165在计算机中的int存储
非常简单明了,好理解。把10进制转换2进制,直接存进去就ok,前面空位补0。
例2:请写出-165(10进制)使用32位int型存储在计算机中的形式。
这是一个负数,按照惯例int型首位为符号位。0表示在正数,1表示负数。
如下:
但在计算机中,负数存储的是补码,不是原码。
他们之间按照如下转换:
原码:1000 0000 1010 0101
反码:1111 1111 0101 1010 (除了符号位,其它取反)
补码:1111 1111 0101 1011 (在反码的基础上加1即可)
那么-165在32位int型中是这样存储的(中间的1,我省略了):
-165在计算机中的int存储
比正数复杂了一点,但是还是可以很容易算出来的。
问题来了?为啥负数要用补码?这不是挑事吗?原码不好吗?
原因之一在于,我们计算: ,在计算机中存储的是二进制,
如果使用原码进行计算,需要单独把符号位拿出来,再做减法运算,而把符号位区分出来是需要额外的硬件电路支撑的,这很不方便。
如果使用补码,如下所示(这里按照16位进行举例):
0000 0000 1010 0101 + 1111 1111 0101 1011=0000 0000 0000 0000;
使用补码参与运算后,无需再管符号位,可以让符号位直接参与运算。这就是使用补码的最大的好处。到这里大家有没有发现,int型的这种存储方案是没有考虑小数的,所以这是整型的。关于int型的存储,不再赘述,整体来说还是清晰明了的一种方案。
2、float对32个坑是如何使用的?
同样也是占用32个坑,float型的范围比int就大很多,而且还能表示小数,那么它到底是如何利用这32个坑的呢?
例3:请写出165.25(10进制)使用float型存储在计算机中的形式。
同样我们还是先转换为2进制: 1010 0101 . 0100
那么如何把上面的二进制小数存到32个坑里呢?
在填坑之前,我们先要规范二进制小数的表示形式,就和我们的科学计数法一样的道理。
(就像 要写成 这个样子,把所有的小数换成统一的格式)
IEEE754标准做了这样的规定:当尾数(小数)不为0时,尾数域的最高有效位为1,这称为浮点数的规格化。
例如:
规格化后的二进制小数,有了统一的规格,可以发现这样规格化之后,我们只需要存储一个尾数(即小数部分,整数部分恒为1)和指数部分。
IEEE754标准把float型的32个坑做了如下划分:
其中包含了1位符号位S,8位阶码E和23位尾数M。
,要存储这个二进制小数;
首先符号位S,0表示正数,1表示负数。S=0;
再写出尾数M,即:M=0100 1010 1000 0000 0000 000;
然后算出阶码E,这里指数为:e=7=0000 0111,根据标准要求,E=e+127;
即:E=7+127=134=1000 0110;
那么把这三个数都填进坑里,就ok啦。
165.25在计算机中的float储存
这个计算过程稍微复杂点,但也还可以手算出来。
但是问题又来了:
1、浮点数的表示范围有多大?
2、为什么要用指数加上127,才是阶码E,而不是直接用指数存进去?
3、这个过程可以看出float有效位是尾数M加1也就是24位,阶码E只是我们规范科学计数法记录指数的,但int有效位是32位,float实际有效位比int少,那么在相互转换的过程中会出现什么问题?
我依次解释这3个问题:
1、浮点数的表示范围有多大?
float型定义的正无穷大
float型定义的负无穷小
可以得出当E= 1111 1111时,指数为255-127=128,但这并不是表示这个数是: ,在IEEE754把这种情况定义为无穷大,此时尾数必须全部为0,不能有其他值,否则就认为无效数字。
那么除了无穷大这个特殊的、人为定义的情况,float型能表示的最大的正整数是多少?最小的负整数是多少?当E= 1111 1111时,是IEEE754定义的特殊值即为无穷大,那么除此之外的最大值就是:E= 1111 1110,M也取最大值,即得到如下结果:
float型能存储的最大正整数
此时阶码E为254,指数即为e=254-127=127。这个数即为:
;
对于尾数我们可以换一个写法:
1.1111 1111 1111 1111 1111 111=10-0.0000 0000 0000 0000 0000 001
这样尾数可以写成: ;
那么float能够表示的最大正整数就是: ,即为 。
那么float能够表示的最大负整数就是: 。
2、为什么要用指数加上127,才是阶码E,而不是直接用指数存进去?
这就很容易说明了,我们举个例子:
例4:请写出0.75(10进制)使用float型存储在计算机中的形式。
写成二进制: 。再写成规划化的计数法: ;
发现问题了没有?这次的指数是个负数啦,而我们希望存储到机器里的阶码永远都是正值,因为我们不希望再浪费一个坑去保存阶码的正负号,于是乎,干脆把指数加上127,而指数能取到的最小值就是-127,这样就可以保证阶码E永远都是正数啦,我们就不用再考虑指数正负号的问题了。
E=-1+127=126=0111 1110;
M=1000 0000 0000 0000 0000 000;
0.75在计算机中的float储存
3、这个过程可以看出float有效位是尾数M加1也就是24位,阶码E只是我们用于规范科学计数法记录指数的,但int有效位是32位,float实际有效位比int少,那么在相互转换的过程中会出现什么问题?
通过问题1知道,float型的表示范围是比int大很多的,但有效位确实只有24位。既然float范围大,那么所有的int型都是可以转换为float型的,这是不会产生溢出报错的。但因为int型有效位是32位,是比float型的24位大的,是有可能发生舍入的,即当一个int型数字,转成float型后,可能就不再是原本数字了,损失了一定的精度。
例如2进制int型正数:0111 1111 1111 1111 1111 1111 1111 1111;
写成科学计数法即为:
小数点后面有30个1,但是我们知道float种尾数M只有23个坑。
则转化为float型后,阶码E=30+127=157=1001 1101
可以发现,我们对原int型中存储的数字只保留了小数点后23个1,而后面7个,直接忽视了,这就是发生了舍入。
如果既要范围大,还要保留精度,那就上双精度浮点型double,double型的存储规则和float型是十分类似的。double型有64个坑位,包括了1个符号位S,11个阶码位E和52个尾数位M。所以double的有效位有53位,可以完整保留int。
ok,同样是占32个坑,那凭啥你float就比int的范围更大?因为float型虽然范围大,但是精度不足啊!所以各有千秋哦。