当前位置: 首页 > news >正文

探索浮点数在内存中的存储(附带快速计算补码转十进制)

目录

一、浮点数在内存中的存储

1、常见的浮点数:

2、浮点数存储规则:

3、内存中无法精确存储:

4、移码与指数位E:

5、指数E的三种情况:

二、快速计算补码转十进制

1、第一种方法讨论:

2、第二种方法讨论:

3、第三种方法讨论:

4、第四种方法讨论:

一、浮点数在内存中的存储

1、常见的浮点数:

首先C语言中的浮点数是什么呢?说白了就是小数,这样的小数在C语言中主要有两种表示:

3.14159//这种是常见的浮点数,以小数形式出现
3.14E10//这种是科学计数法中的表示,浮点数为3.14×10^10,或写成3.14E+10

      既然整型在计算机中有表示范围,那么浮点数其实也是有范围的,我们可以在<limits.h>文件和<float.h>文件中查找到整型和浮点型的表示范围。那么整型和浮点型在内存中的存储相同吗?我们根据一段代码进行分析。

 int main()
 {
     int n = 9;
     float* pf = (float*)&n;
     printf("n = %d\n", n);//打印结果为:n = 9
     printf("*pf = %f\n", *pf);//打印结果为:*pf = 0.000000
 ​
     *pf = 9.0;
     printf("n = %d\n", n);//打印结果为:n = 1091567616
     printf("*pf = %f\n", *pf);//打印结果为:*pf = 9.000000
     return 0;
 }

注:上面的代码是在VS编译器的x64环境下的运行结果。

       这个代码得到了让人意想不到的结果,第1条和第4条printf()语句的输出结果自然不必多说,那么第2条和第3条的执行结果说明,整型数据和浮点型数据的存储方式是不同的

2、浮点数存储规则:

       根据国际标准IEEE754,可以将任意一个浮点数V都写成(-1)^S×M×2^E,其中(-1)^S表示符号位,当S为0时,V为正数,S为1时,V为负数。M表示有效数字,规定范围在1和2之间,2^E表示指数位。

补充:探究二进制整数和小数的权重

    1111 1111 . 1111,整数部分的权重由小到大(从右向左)依次是2^0=1、2^1=2、2^2=4、2^3=8、2^4=16、2^5=32、2^6=64、2^7=128,小数部分权重由大到小(从左向右)依次是2^(-1)=0.5、2^(-2)=0.25、2^(-3)=0.125、2^(-4)=0.0625。

        比如5.5,转换成二进制是101.1,这样表示不是科学计数法所规定的,我们将小数点向左移动两位(称为左规两位),变成1.011×2^2(左规次方为正数,右规次方为负数,这样看,V=(-1)^0×1.011×2^2,其中S=0,M=1.011,E=2。

        这里我们就可以看出,为什么任何一个浮点数的M都是在1到2之间?因为对于任意一个数转换成二进制,最高位一定是1(因为如果是0可以不写),这样左规几次之后,M得到的值一定在1和2之间。

      下面将5.5存储到内存中,IEEE754规则如下,对于32位浮点数(float类型),使用1个比特位用来存放符号位S,用8个比特位存放指数位E,剩下的23个比特位存放有效数字M,但由于M的范围确定,所以最高位的1(整数部分的1)通常不需要存储,所以实际上可以存储24位有效数字。对于64位浮点数(double类型),使用1个比特位存放符号位S,11个比特位存放指数位E,剩下的52个比特位存放有效数字M,同样省略了M的最高位。

3、内存中无法精确存储:

       对于一些浮点数,计算机是无法准确进行存储的,比如5.3,它的小数部分0.3转换成二进制可能有非常多的位数(0.3的二进制为 0.010011 0011 0011...,1001部分无限循环),所以计算机存储的也是近似的数据。

//浮点数在内存中的存储
 int main()
 {
     float f = 5.3f;
     return 0;
 }

4、移码与指数位E:

       首先,E表示指数位,就一定是一个无符号整数,如果E有8位,那么范围是0~255,E有11位,范围是0~2047。但是E有可能是负数,比如0.5的科学计数法表示为1.0×2^(-1),此时E=-1。

       为了避免这种情况的发生,我们将8位的E加上偏移量127,11位的E加上1023,这样就可以保证E一定为正数,这样的E的二进制表示称为移码表示,所以我们可知,加上偏移量的E的移码就一定为正数,但移码不止在浮点数中表示阶码,其他地方也有所体现,所以移码不一定为正数。

//浮点数在内存中的存储
 int main()
 {
     float f = 5.5f;
     //5.5的二进制表示101.1,科学计数法表示1.011×2^2
     //S=0(正数),M=011 0000 0000 0000 0000 0000,E=2+127
     //二进制0 1000 0001 011 0000 0000 0000 0000 0000
     //对齐后结果0100 0000 1011 0000 0000 0000 0000 0000
     //对应的十六进制40 b0 00 00
     return 0;
 }

下面分析我们来最初的代码,代码如下。

int main()
 {
     int n = 9;
     float* pf = (float*)&n;
     printf("n = %d\n", n);//打印结果为:n = 9
     printf("*pf = %f\n", *pf);//打印结果为:*pf = 0.000000
 ​
     *pf = 9.0;
     printf("n = %d\n", n);//打印结果为:n = 1091567616
     printf("*pf = %f\n", *pf);//打印结果为:*pf = 9.000000
     return 0;
 }

先看第3条printf()语句。

//第3条printf()语句相当于下面的代码
 int main()
 {
     float f = 9.0f;
     //9.0的二进制是1001.0,科学计数法表示为1.0010×2^3
     //S=0,M=001 0000 0000 0000 0000 0000,E=3+127
     //二进制为0 1000 0010 001 0000 0000 0000 0000 0000
     //整理后0100 0001 0001 0000 0000 0000 0000 0000
     //十六进制为41 10 00 00
     //为什么输出1091567616呢?
     //这是因为计算机把0100 0001 0001 0000 0000 0000 0000 0000当成有符号数
     //2^20+2^24+2^30=1048576+16777216+1073741824=1091567616
     return 0;
 }

5、指数E的三种情况:

如果拿到一串浮点数的二进制数,计算E就分为下面三种情况。

1)E不为全0,也不是全1:

     拿到E的部分,比如1000 0001,用这个数的十进制减去127后,得到的就是E原来的值,即129-127=2,原来的E=2。

2)E为全0:

       此时的E如果是8位,则真实值是1-127=-126,如果是16位,则真实值是1-1023=-1022,实际上这个小数已经是非常非常小的一个数了,无限接近于0,那么计算机会将这样的小数按0来处理,这时的有效数字M不再加上第一位的1,而是还原为0.xxxxx...的小数,这样做为了表示±0。也就是E、M全0时,这样的小数就按0处理。

3)E为全1:

如果E全为1,此时M全为0,这样的数字表示±∞,正负取决于符号位S。

这样特殊的数就有8种表示:

SEM
000+0
100-0
00≠0非规格化正数
10≠0非规格化负数
010+∞
110-∞
01≠0NaN
11≠0NaN

非规格化数字是计算机中一种特殊的数字。

NaN(非数)也是一种特殊的数字,含义是无定义的数或不可表示的数,比如0/0、∞/∞、∞-∞等等返回的都是一个非数。

下面就可以分析上面代码的第2条printf()语句了。

//第2条printf()语句
 int main()
 {
     int n = 9;
     printf("%f\n", n);//打印结果为:0.000000
     //9的二进制为0000 0000 0000 0000 0000 0000 0000 1001
     //其中E的部分是0000 0000,全为0
     //M的部分是000 0000 0000 0000 0000 1001
     //科学计数法表示为0.000 0000 0000 0000 0000 1001 × 2^(-126),这已经是非常小的数字了
     //而且%f只能打印小数点后六位,结果自然是0.000000
     printf("%f\n", (float)n);//打印结果为:9.000000
     return 0;
 }

二、快速计算补码转十进制

       首先,只有负数才有补码,正数的补码就是原码,直接按照原码转换成十进制即可,所以下面计算的均是负数的补码转成十进制(《最快10秒钟就可以完成》)

我们先看如何转换,之后在讨论是为什么这么转换。

目前,拿到一个负数的补码,我知道的有4种方法可以转换成十进制:

1、补码转成原码,再转换成十进制,比如1100 0101[补] 转成 1011 1011[原],再转换成十进制就是-(1+2+8+16+32)=-59

2、用最高位的位权依次加上其他位的值,比如1100 0101转换成十进制,-128+(1+4+64)=-59

3、先求无符号数,再用256减去无符号数的值,最后取负数即可,比如1100 0101转换成十进制,-(256-(1+4+64+128))=-59

4、直接按照补码进行计算,按照有0的位权相加,再加1,最后取负(这是我认为最快的方法),比如1100 0101转换成十进制,-(2+8+16+32+1)=-59

      个人觉得第1种最常用,但是比较慢,第2、3种实际上差不多(第三种甚至比第二种还慢),第4种最快(因为对我们来说,取负操作要比含有负数的减法更受欢迎)。

       下面补充一种更快的计算数字相加的方法,是不是经常对于1+2+8+16+32这样的一串数字相加比较头疼,今天快速计算方法,8以下的数字一起计算(一眼就能算出结果),128以下的数字一起计算(绝大多数都是最大计算到128),那么剩下的就是8+16+32+64这种,只需将16看成2×8,32看成4×8,64看成6×8即可,比如1+2+8+16+32=3+(1+2+4)×8=56+3=59

下面来讨论到底是为什么可以这么转换,如果对这里不感兴趣的同学,现在可以划走了~

1、第一种方法讨论:

       实际上,我也不知道为什么要这样转换,在学计算机的时候,就接触到这种方法了,但是用着用着感觉比较慢,于是开发出了其他的方法。

2、第二种方法讨论:

      对于正数的原码,其实每一位是有对应的位权的,比如1001[原],对应的十进制就是2^0+2^3=9。我们知道,对于负数的补码来说,二进制的最高位表示符号位,它的位权是一个负值,比如1001[补],对于的十进制就是-2^3+2^1=-7(每一位的位权不变,只是最高位是个负数)。

3、第三种方法讨论:

这种方法是我观察char类型和unsigned char类型数据的规律中得出的,数据如下:

char二级制(补)十进制unsigned char二级制(补)十进制
0000 000000000 00000
0000 000110000 00011
0000 001020000 00102
0000 001130000 00113
............
0111 11101260111 1110126
0111 11111270111 1111127
1000 0000-1281000 0000128
1000 0001-1271000 0001129
............
1111 1100-31111 1100253
1111 1110-21111 1110254
1111 1111-11111 1111255

       不难看出,char类型可以存储127个正数,128个负数,还有一个0,一共256个数据,所能表示的范围是-128~127。unsigned char类型可以存储255个正数和一个0,也是256个数据,所能表示的范围是0~255。

       同时上面的两组数据也有一些规律,观察有符号和无符号的十进制,当最高位为1时,对应的绝对值之和总是256,比如1000 0001[补]是-127[有]或129[无],可以写成-127 = -(256-129),而129用2^7+2^1计算。

       那么以后计算有符号数比较麻烦时,可以这样快速计算,即用256减去对应的无符号数,再取负,得到对应的有符号数。比如1011 0110[补],无符号数是2^1+2^2+2^4+2^5+2^7=2+4+16+32+128=182,有符号数就是-(256-182)=-74。

这种计算方法的优点是把对应的无符号数也计算出来了,缺点还是比较慢。

4、第四种方法讨论:

       还有一种计算有符号数的方法,观察1000 0000 + 0111 1111结果为1111 1111,十进制为-1,我们可以找镜像,即把这个负数变成对应的正数(1变0,0变1),比如1011 0110,镜像过去的正数是0100 1001,用-1减去这个正数得到的就是负数,即-1-(1+8+64)=-74

      这种方法与原反补转换计算有符号数几乎相同,但我们可以找捷径计算,直接拿到有符号数1011 0110,按照位权相加,此时要看有0的位置,即1+8+64=73,最后加1再取负数,即-(73+1)=-74,得到的就是有符号数的十进制大小。

        第四种说白了就是既然转成原码后可以按照“1”位置的位权相加,那么如果不转成原码呢?我们就可以看“0”位置的位权相加(反正一个负数的二进制最高位是1,看“0”的位权没有影响)。之后转成原码要加1,那我们不转也要加1(是不是有同学想成减1了,其实这时候不加1的值和原码不加1计算出来的值是一样的),最后整体加个负数即可。听懂掌声~

相关文章:

  • 网易云音乐分布式KV存储实践与演进
  • 博客系统完整开发流程
  • 观成科技:海莲花“PerfSpyRAT”木马加密通信分析
  • RK3399 Android10双WiFi功能实现
  • ubuntu+aarch64+dbeaver安装【亲测,避坑】
  • low rank decomposition如何用于矩阵的分解
  • Jenkins protoc: command not found
  • Redis面试题----Redis 的持久化机制是什么?各自的优缺点?
  • 计算机毕业设计SpringBoot+Vue.js中小型医院网站(源码+文档+PPT+讲解)
  • Go语言中的信号量:原理与实践指南
  • 岳阳市美术馆预约平台(小程序论文源码调试讲解)
  • opencv边缘检测
  • 利用机器学习实现实时交易欺诈检测
  • 学习Java数据类型:全面解析与实践指南
  • 【HarmonyOS Next】鸿蒙应用公钥和证书MD5指纹的获取
  • 客户端进程突然结束,服务端read是什么行为?
  • 计算机三级网络技术备考
  • Android 字体大小自动处理 AppCompactTextView 和 自定义 TextView
  • 密码学基础
  • 【c语言】字符函数和字符串函数(1)
  • 泽连斯基:俄代表团级别低,没人能做决定
  • 高途一季度净利润同比增长1108%: “与吴彦祖一起学英语”短时间内就实现了盈利
  • 海昏侯博物馆展览上新,“西汉帝陵文化展”将持续展出3个月
  • 美国务卿会见叙利亚外长,沙特等国表示将支持叙利亚重建
  • 获派驻6年后,中国驻厄瓜多尔大使陈国友即将离任
  • 为什么越来越多景区,把C位留给了书店?