惊!浮点数在内存中竟然是这么存储的?

目录

1. 浮点数在计算机内部的表示方法

2. 单双精度浮点数存储模型

2.1 单精度浮点数模型

2.1.1 M的值如何确定

2.1.2 E的值如何确定

2.2 双精度浮点数模型

2.3 开篇所提到的问题


大家好,上期文章给大家带来了关于整数在内存中是如何存储的这一话题,阅读量和点赞量都超乎了我的想象,看来大家对于这个知识点还是很感兴趣的。 

那有同学可能会问了,了解这些有什么用呢,写代码的时候又用不上。

我的回答是:学习计算机底层的知识非常有必要。你只有了解数据在内存中是如何存储的,你才能更好的去编写C语言相关的代码,并且在程序出现Bug的时候,你也知道如何去调试代码。因为C语言是可以直接对内存进行操作的一门编程语言,所以学习这些东西是非常有必要的。


那么今天呢,我就给大家带来浮点数在内存中是如何存储的这一知识点,希望大家能够喜欢,在这里先感谢大家的支持。

在进行讲解之前,我想先了解一下大家对这个知识的掌握程度。

int main()
{
     int n = 9;
     float *pFloat = (float *)&n;
     printf("n的值为:%d\n",n);

     printf("*pFloat的值为:%f\n",*pFloat);


     *pFloat = 9.0;
     printf("num的值为:%d\n",n);

     printf("*pFloat的值为:%f\n",*pFloat);

     return 0;
}

大家认为这个代码的输出结果是什么呢?如果感到分析起来很吃力,不要着急,接下来我会慢慢讲解。

在这里,我先给出程序运行的结果:

 我相信会有不少同学发出疑问,明明num和*pFloat中存储的是同一个数,为什么输出结果却不同呢?

那么,我们就正式进入今天的讲解 --- 浮点数的存储规则

1. 浮点数在计算机内部的表示方法

相信有部分聪明的同学已经猜到,造成以上结果的原因是浮点数在内存中的存储方式与整数不同

我们在上期的文章中已经讲过整数在内存中的表示方法,忘记的同学可以翻看我之前的博客,链接在这里:整形数据在计算机内部到底是如何存储的?

但是浮点数的存储跟整数就完全不同了,也不再像整数存储的那么简单了。

根据国际标准IEEE(电气和电子工程协会)754规定,任意一个浮点数V可以表示成以下的形式:

  • (-1) ^ S * M * 2 ^ E
  • (-1) ^ S 代表符号位,当s = 0时,V为正数,当s = 1时,V为负数
  • M表示有效数字,在1到2之间
  • 2 ^ E表示指数位

有同学可能会疑惑,为什么叫浮点数,简单来说就是,小数点可以浮动的数字,就叫做浮点数

比如 111.11这个数字,它可以写成11.111 * 10,也可以写成1.1111 * 10 ^ 2

但是根据IEEE的规定,M必须在1到2之间,所以在存储浮点数的时候,我们只能将他看做1.1111* 10 ^ 2。

当然,这个例子的数字不太好看,我们先举一个简单的例子。

例如:十进制的5.0,在计算机内部是如何存储的呢?

5.0写成二进制数就是101.0,我们要将有效位数控制在1到2之间,所以可以写成1.010 * 2 ^ 2。

切记这里一定是2的多少次方,同学们可能接触十进制的时间比较长,可能容易写成10 ^ 2,这里要注意一下。

又因为5.0是一个整数,所以符号位的S = 0

所以 S = 0,M = 1.010,E = 2。

那么,有同学可能又问了,我们知道了S M E之后,那这些数字具体又是如何存储的呢,他们的之间的顺序是如何呢?别着急,接下来我就要讲了。

2. 单双精度浮点数存储模型

2.1 单精度浮点数模型

我们还是拿十进制的5.0举例,化成二进制之后就是1.010 * 2 ^ 2,加上符号位就是(-1) ^ 0 * 1.101 * 2 ^  2,所以 S = 0,E = 2,M = 1.101。

2.1.1 M的值如何确定

我们在前面说过,1\leq M\leq 2,也就是 M = 1.xxxxxxxxx,其中的xxxxxxxxx表示小数部分。

IEEE754规定,在计算机内部存储M的时候,默认这个数的第一位为1,只保留后面的小数部分,比如在保存1.01的时候,只保存小数部分01,等到读取的时候,再把前面的1加上去,这么做的好处是节省一位有效数字,可以大大提高浮点数的精度。

2.1.2 E的值如何确定

首先,E是一个无符号整数,这意味着,在float的数据类型中,E有8位,所以E的取值范围是0 - 255。但是我们知道,如果浮点数是一个小于1的数,那么意味着浮点数的指数,也就是E,肯定是一个小于0的数,那这个时候怎么办呢?

所以IEEE754规定,存入内存中的E的真实值必须再加一个中间数,对于8位的E,这个中间数是127。至于为什么是127呢,大家可以想一想,这里表达一下我自己的观点。

首先,在单精度浮点数中,指数部分的位数为8位,因此可表示的范围是从0 - 255,为了使指数部分能够表示负数,需要将偏移量选为指数范围的一半,从而保证正数和负数的对称性,这样一来,指数部分的取值范围变为-127到128,其中0表示指数-127,255表示指数128。

同理,指数E从内存中取出还分三种情况

  • E不全为0或不全为1

这时候,浮点数的指数采取以下的计算规则:E - 127 = 真实值,再将M前面的1补上。

举个例子来说,0.5的二进制序列为0.1,因为1\leq M\leq 2,所以化成1 * 2 ^ -1。

所以,对于0.5的二进制序列来说,S = 0,E = -1 + 127 = 126,M = 1,所以其二进制表示形式为:

0 01111110 00000000000000000000000

又因为1Byte = 8 bit,我们将他合并在一起,并转化位16进制得:

0011 1111 0000 0000 0000 0000 0000 0000
  3    F    0    0    0    0    0    0

十六进制表示为:0x3F000000
  •  E全为0

这时,浮点数的E = 1 - 127即为真实值,

有效数字M不再加上第一位的1,而是还原成0.xxxxxxxxx的小数,这样做是为了表示 \pm 0 ,以及接近于0的很小的数字。

  • E全为1

这时,如果有效位数M = 0,表示(正负)无穷大

2.2 双精度浮点数模型

 double 跟 float类型相似,只不过精度更大一些,需要注意的是,此时的E占有11个bit,M占有52个bit,在内存中存储E的时候,需要加上的偏移量为1023,其余跟float定义的规则类似,在这里我就不过多讲解了,大家下来可以自行推导。

好了,关于浮点数规则的讲解就到这里。

2.3 开篇所提到的问题

讲完了浮点数在内存中是如何存储的,下面让我们回到最开始我抛出的那个问题。 

int main()
{
     int n = 9;
     float *pFloat = (float *)&n;
     printf("n的值为:%d\n",n);

     printf("*pFloat的值为:%f\n",*pFloat);


     *pFloat = 9.0;
     printf("num的值为:%d\n",n);

     printf("*pFloat的值为:%f\n",*pFloat);

     return 0;
}

让我们一点一点来分析:

 首先,定义了一个整数n,他内存中存储的值为9,所以打印n,结果为9,没毛病。

那么,当我们取出n的地址,用一个浮点型的指针变量来接收的时候,编译器会认为你这是一个浮点数,所以使用了浮点数的存储规则去存储他,那么具体是如何存储的呢,让我们一点一点来分析。

// 首先,9的二进制序列为:
// 00000000000000000000000000001001 -> 原码,反码,补码

// 把9看成浮点数来存储
// 0    00000000   00000000000000000001001
// S       E              M

// 我们之前说过,E如果全为0,E就等于1 - 127 = -126,M的第一位也不用补1
// 所以 S = 0,E = -126,M = 00000000000000000001001
// 所以该浮点数的大小为 (-1) ^ 0 * 00000000000000000001001 * 2 ^ -126

// 可想而知,2的-126次方是多么的小啊
// 因为float默认的精度是小数点后6位,所以你看到程序的执行结果为0.000000,并不是程序出现了错误

前两个数的打印结果我们解释清楚了,那么我们来看后两个的输出结果。

首先,指针变量解引用pFloat,将其变成9.0,但是打印n的时候使用的是%d的打印方法,所以编译器还是默认你存储的是一个浮点数,而你想要的却是一个整数,所以造成n的值发生很大的改变,那么具体是如何改变的呢,让我们一起来分析一下

// 首先,十进制浮点数9.0写成二进制为1001.0

// 然后,使用科学计数法将它化成 1.001 * 2 ^ 3

// 其次,用IEEE754标准可以写成(-1) ^ 0 * 1.001 * 2 ^ 3

// S = 0,E = 3,M = 1.001

// 此外,在存储的时候,E要加上偏置值127,所以E = 127 + 3 = 130,M = 0.001,只保留小数点后面的位数
// 所以二进制的表示形式为 0 10000010 00100000000000000000000

// 所以n的二进制序列为 0100 0001 0001 00000000000000000

// 合并到一起为 01000001000100000000000000000000,可想而知,这是一个非常大的数字。

// 而这个数,还原成十进制,刚好是1091567616

// 我们心中的疑惑终于解开了!

好了,那么今天有关浮点数的讲解到这里就结束了,大家在看完今天的文章之后,也可以把我上篇文章有关整数存储的内容再复习一下,两者都是比较重要的内容,希望大家能够掌握。

如果你喜欢本篇文章,记得点赞,转发加关注哦!我们下期再见!

  • 11
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值