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

news/2025/2/27 1:04:31

目录

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

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计算出来的值是一样的),最后整体加个负数即可。听懂掌声~


http://www.niftyadmin.cn/n/5869334.html

相关文章

货车一键启动无钥匙进入手机远程启动的正确使用方法

一、移动管家货车无钥匙进入系统的使用方法 基本原理&#xff1a;无钥匙进入系统通常采用RFID无线射频技术和车辆身份识别码识别系统。车钥匙需要随身携带&#xff0c;当车钥匙靠近货车时&#xff0c;它会自动与货车的解码器匹配。开门操作&#xff1a;当靠近货车后&#xff0…

AI Agent Service Toolkit:一站式大模型智能体开发套件

项目简介 该工具包基于LangGraph、FastAPI和Streamlit构建,提供了构建和运行大模型Agent的最小原子能力,包含LangGraph代理、FastAPI服务、用于与服务交互的客户端以及一个使用客户端提供聊天界面的Streamlit应用。用户可以利用该工具包提供的模板快速搭建基于LangGraph框架…

WebSocket简单介绍 并接入deepseek

目录 什么是 WebSocket&#xff1f;工作原理&#xff1a; 为什么需要 WebSocket&#xff1f;WebSocket 的优势HTTP 和 WebSocket 的区别WebSocket 的劣势WebSocket 的应用场景WebSocket 握手过程1. 客户端发起握手请求2. 服务器响应握手请求3. 建立连接 WebSocket 事件处理WebS…

机器视觉--相机曝光

在现代工业生产的精密舞台上&#xff0c;机器视觉技术已然成为推动生产自动化、智能化的关键力量。而工业相机作为机器视觉系统的 “眼睛”&#xff0c;其曝光环节更是决定了视觉信息获取的质量与精度&#xff0c;如同为工业生产赋予了一双洞察入微的 “智慧之眼”&#xff0c;…

C# httpclient 和 Flurl.Http 的测试

关于C#调用接口或Post,Flurl封装了httpclient, CSDN有哥们提供了一个公网的测试网站&#xff0c;可以测试Post调用&#xff0c;我写了2个函数&#xff0c;测试httpclient和Flurl使用Post: async 和 await 是成对使用的&#xff0c;为了接受web异步返回的数据&#xff0c;winfor…

The difference of sort() and sorted() in Python

This is my first time writing a blog in English, so please forgive me for not writing well The sort() method and sorted() function in Python both sort elements, but they differ in usage and behavior: ​1. Type and Usage: ​sort(): A ​list method​ (only …

Jtti.cc:站群服务器SEO优化建议,如何分配多IP?

站群优化的核心目标之一是尽可能通过多个网站互相引导流量&#xff0c;从而提升主站的权重。这时候&#xff0c;多IP的分配至关重要&#xff0c;因为搜索引擎会检测到同一IP下的网站之间的关联性。如果一个IP地址下有过多的相似站点&#xff0c;搜索引擎可能会认为这些站点存在…

‌XPath vs CSS Selector 深度对比

&#x1f4ca; ‌核心差异总览‌ ‌对比维度‌‌XPath‌‌CSS Selector‌‌语法复杂度‌较高&#xff08;需路径表达式&#xff09;简洁&#xff08;类似前端开发习惯&#xff09;‌性能‌较慢&#xff08;全局遍历&#xff09;更快&#xff08;浏览器原生优化&#xff09;‌文…