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

news/2025/2/27 14:37:13/

目录

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

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 = 9printf("*pf = %f\n", *pf);//打印结果为:*pf = 0.000000​*pf = 9.0;printf("n = %d\n", n);//打印结果为:n = 1091567616printf("*pf = %f\n", *pf);//打印结果为:*pf = 9.000000return 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 00return 0;}

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

int main(){int n = 9;float* pf = (float*)&n;printf("n = %d\n", n);//打印结果为:n = 9printf("*pf = %f\n", *pf);//打印结果为:*pf = 0.000000​*pf = 9.0;printf("n = %d\n", n);//打印结果为:n = 1091567616printf("*pf = %f\n", *pf);//打印结果为:*pf = 9.000000return 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=1091567616return 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.000000printf("%f\n", (float)n);//打印结果为:9.000000return 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.ppmy.cn/news/1575263.html

相关文章

九、数据治理架构流程

一、总体结构 《数据治理架构流程图》&#xff08;Data Governance Architecture Flowchart&#xff09; 水平结构&#xff1a;流程图采用水平组织&#xff0c;显示从数据源到数据应用的进程。 垂直结构&#xff1a;每个水平部分进一步划分为垂直列&#xff0c;代表数据治理的…

【算法系列】归并排序详解

文章目录 归并排序详解1. 基本原理1.1 分治法策略1.2 归并排序步骤1.3 图解示例 2. 时间复杂度与空间复杂度2.1 时间复杂度2.2 空间复杂度 3. 稳定性4. Java 实现示例5. 归并排序的优点与缺点5.1 优点5.2 缺点 6. 总结 归并排序详解 归并排序&#xff08;Merge Sort&#xff0…

OpenGL 04--GLSL、数据类型、Uniform、着色器类

一、着色器 在 OpenGL 中&#xff0c;着色器&#xff08;Shader&#xff09;是运行在 GPU 上的程序&#xff0c;用于处理图形渲染管线中的不同阶段。 这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说&#xff0c;着色器只是一种把输入转化为输出的程序。着色器…

【愚公系列】《Python网络爬虫从入门到精通》035-DataFrame数据分组统计整理

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…

伪404兼容huawei生效显示404

根据上述思考&#xff0c;以下是详细的中文分步说明&#xff1a; --- **步骤 1&#xff1a;获取目标设备的User-Agent信息** 首先&#xff0c;我们需要收集目标设备的User-Agent字符串&#xff0c;包括&#xff1a; 1. **iPhone设备的User-Agent**&#xff1a; Mozi…

【C++】unordered系列容器的模拟实现

文章目录 Ⅰ. 前言Ⅱ. 对哈希表的改造一、模板参数列表的改造二、哈希表的迭代器① 迭代器的基础框架② 迭代器的常见函数实现③ 哈希表对迭代器的利用☠ 一个小坑&#xff0c;注意在哈希表中 typedef ... iterator 的时候&#xff0c;记得要将 typedef 语句放在 public 权限中…

【Python爬虫(83)】探秘an网数据爬取:合法合规下的技术探索

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…

基于Springboot的小说网站【附源码】

基于Springboot的小说网站 效果如下&#xff1a; 系统主页面 书库信息页面 书籍详情页面 推荐信息页面 小说推荐页面 书库信息页面 小说排行榜页面 系统管理页面 研究背景 随着互联网技术的快速发展&#xff0c;网络文学逐渐成为一种新兴的文学形式&#xff0c;吸引了大量读…