欢迎光临散文网 会员登陆 & 注册

C语言浮点型数据在内存中的存储方式!

2022-04-18 15:51 作者:北岸已春山  | 我要投稿


直接来吧!

常见的整形有int,unsigned int,signed int等,但在内存中存储时,char类型也可以归为整形家族。

因为char类型是以ASCII码的形式存储在内存当中的,而ASCII码是整形。

整形数据在内存中是以二进制的补码形式来存储的,但详细并不在该篇中,今天我们来说一下浮点型在内存中的存储方式。

在C语言中,形如float,double等数据类型为浮点型也叫实型,而整形和浮点型在内存中的存储方式是不一样的。

首先,我们要知道,数据在内存中是以二进制的形式存储的。

根据标准(IEEE 754)定义,任何一个二进制浮点数X,可以表示为以下形式:

公式:(-1)^S * M * 2^E

其中(-1)^S表示符号位,当S=0,X为正数;当S = 1,X为负数

M表示有效数字;M的取值范围是大于等于1,小于2的数;1 ≦ M < 2。

2^E表示指数位

标准规定:

32位(单精度浮点):最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数M。

64位(双精度浮点):最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数M。

针对M和E,标准还有特别规定:

先来说有效数M:

前面说过,M∈[1,2),也就是说,M可以写成1.xxx的形式,其中xxx表示小数部分。

这样就造成了在计算机内部保存M时,默认这个数的第一位总会是1,既然是恒定的数字那为什么还要让他存在着浪费内存空间呢?

所以啊,M在计算机内部保存时,会无视这第一位的1,将后面的xxx存入M中,而在取出时再加上这第一位的1,既不影响浮点数在内存中的存储,也不使得内存空间出现浪费。

再来说指数E:

首先,E是一个无符号整形(unsigned int),这意味着,如果E为8位,它的取值范围就是0~255;如果E为11位,它的取值范围就是0~2047.

我们知道,科学计算法中的E是可以出现负数的,所以标准规定,存入内存中E的真实值必须再加上一个中间数。8位的E,这个中间数为127;11位的E,这个中间数为1023。

比如,2^10,E=10,所以保存成32位浮点数时,必须写成10+127=137,即10001001

eg①:浮点数5.0在内存中的存储方式?

已知5.0为正数,S=0

5.0的二进制为101.0,将101.0小数点左移,直至小数点左边的数,符合M的取值范围,得到:M = 1.01

从101.0到1.010,小数点向左移了2位,所以E=2

已知未知数,代入公式:(-1)^0 * 1.01 * 2^2

接下来就是将数据存入内存:


32位:

S占用1字节,S为0,将0存入内存中;

M占用23字节,M为1.01,按标准将1省去得01,存入内存中的就是0100 0000 0000 0000 0000 000(不够补0,直到凑够23位)

E占用8字节,E=2,127+2=129,129的二进制为:1000 0001

存入内存中的顺序依次为:

S -> E ->  M

0 10000001 01000000000000000000000

在内存中的存储形式:0100 0000 1010 0000 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 a0 40(小端字节序。关于大小端字节序,该篇并无讲解)


64位:

S占用1字节,S为0,将0存入内存中;

M占用52字节,M为1.01,按标准将1省去得01,存入内存中的就是0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000;

E占用11字节,E=2,1023+2=1025,1025的二进制为:100 0000 0001。

存入内存中的顺序依次为:

S -> E ->  M

0 10000000001 0100000000000000000000000000000000000000000000000000

在内存中的存储形式:0100 0000 0001 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 00 00 00 00 14 40  (小端字节序。关于大小端字节序,该篇并无讲解)

eg②:浮点数48.5在内存中的存储方式?

我们知道48的二进制怎么计算,那0.5的二进制呢?

不要急,一步一步来,首先48.5为正数,S=0。

小数二进制详解:

解法:乘2取余法

先看0.5的二进制:

0.5*2 = 1 余 0

0.5 的二进制就是 0.1

0.5太简单,看不出来什么,那么我们再来看0.6875的二进制:

0.6875 * 2 = 1 余 0.375

0.375 * 2 = 0 余 0.75

0.75 * 2 = 1 余 0.5

0.5 * 2 = 1 余 0

将所求整数由上而下(从上到下)依次排列得到1011,由于我们求的是小数的二进制,所以要在1011前加上‘ 0. ’,最后得到‘ 0.1011 ’。

知道了方法,掌握了方法,就会进行更深入的研究,我们再来看:

说明:

(须知知识!求一个数的负数次方公式:a^-x=1/a^x)

二进制的1011,通过1*2^0 + 1*2^1 + 0*2^2 + 1*2^3 = 1+2+0+8 = 11

二进制的0.1011,通过

  1*2^-1 + 0*2^-2 + 1*2^-3 + 1*2^-4

= 1*(1/2^1) + 0*(1/2^2) + 1*(1/2^3) + 1*(1/2^4) 

= 1/2 + 0 + 1/8 + 1/16 

= 0.5 + 0 + 0.125 + 0.0625 

= 0.6875

已知48的二进制为110000 ,0.5的二进制为0.1,所以48.5的二进制就是110000.1

将小数点左移,直至符合M的取值范围M∈[1,2),M = 1.100001,共左移5位,E = 5。

已知未知数,代入公式:(-1)^0 * 1.100001 * 2^5

接下来就是将数据存入内存:


32位:

S占用1字节,S为0,将0存入内存中;

M占用23字节,M为1.100001,按标准将1省去得100001,存入内存中的就是1000 0100 0000 0000 0000 000;

E占用8字节,E=5,127+5=132,132的二进制为:1000 0100。

存入内存中的顺序依次为:

S -> E ->  M

0 10000100 10000100000000000000000

在内存中的存储形式:0100 0010 0100 0010 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 42 42(小端字节序)


64位:

S占用1字节,S为0,将0存入内存中;

M占用52字节,M为1.100001,按标准将1省去得100001,存入内存中的就是1000 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000;

E占用11字节,E=2,1023+5=1028,1028的二进制为:100 0000 0100。

存入内存中的顺序依次为:

S -> E ->  M

0 10000000100 1000010000000000000000000000000000000000000000000000

在内存中的存储形式:0100 0000 0100 1000 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 00 00 00 40 48 40(小端字节序)

eg③:浮点数0.5在内存中的存储方式?

0.5为正数,二进制为0.1,M∈[1,2),所以小数点要像右移一位,M=1,E=-1;

(-1)^0 * 1 * 2^-1

接下来就是将数据存入内存:


32位:

S占用1字节,S为0,将0存入内存中;

M占用23字节,M为1,按标准将1省去没了,内存中补零0000 0000 0000 0000 0000 000;

E占用8字节,E=-1,127-1=126,126的二进制为:0111 1110

存入内存中的顺序依次为:

S -> E ->  M

0 01111110 00000000000000000000000

在内存中的存储形式:0011 1111 0000 0000 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 00 3f(小端字节序)


64位:

S占用1字节,S为0,将0存入内存中;

M占用52字节,M为1,按标准将1省去没了,存入内存中的就是0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000;

E占用11字节,E=2,1023-1=1022,1022的二进制为:011 1111 1110。

存入内存中的顺序依次为:

S -> E ->  M

0 01111111110 0000000000000000000000000000000000000000000000000000

在内存中的存储形式:0011 1111 1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000(4位二进制位 = 1位16进制位)

编译器调试地址:0x 00 00 00 00 00 00 e0 3f(小端字节序)

说完存储,我们再来看取出:

将二进制数据以浮点型取出,需要将所做修改尽数还原,最主要的是E:

指数E从内存中取出分成三种情况:

1.E不全为0或不全为1(该情况为正常情况。怎么来的,就怎么回去)

首先,E需要转为十进制并减去(32位:127 或 64位:1023),得到真实值,再将M的1补回,反推公式,得到浮点数。

(2,3较特殊且不常见,如果掌握上述知识,完全可根据自身条件进行推导)

2.E全为0(无穷小)

此时,浮点数的指数E=1-127或1-1023即为真实值,有效数字M不再补回第一位的1,而是还原为0.xxx的小数。

这样做是为了表示±0,以及接近于0的很小的数字。

(0.0000000000000000000000000000001如果真的有一天需要你用手推这种数,那么可能是你穿越了)

3.E全为1(无穷大)

此时,如果有效数字M全为0,表示±∞(正负取决于符号位S)

eg④:32位:0100 0000 1010 0000 0000 0000 0000 0000以浮点数取出赋给a,a=_?

用已知知识反推,将二进制串分割成易读的:0 10000001 01000000000000000000000

M补1 = 1.01000000000000000000000 = 1.01

E = 1000 0001 = 129 -> 129-127 = 2

反推公式:(-1)^S * M * 2^E = (-1)^0 * 1.01 * 2^2(可有可无)

答:M = 1.01 -> 右移2位得 101.0 转回十进制 5.0


感谢大家能看到这里!


C语言浮点型数据在内存中的存储方式!的评论 (共 条)

分享到微博请遵守国家法律