关于整数和浮点数在内存中的存储

在数据存储中,整数与浮点数在内存中的存储方式有很大的差别,下面我们将分别探讨它们的存储方式。

目录

1.整数在内存中的存储

2.大小端字节存储和字节序判断

        2.1何为大小端?

        2.2设计程序判断机器的字节序存储方式

3.整型提升与补码的深层含义

        3.1    在c语言中,整型算术运算总是至少以缺省整型类型(int / unsigned int)的精度来进行的。为了获得这个精度表达式中字符型(char)和短整型(short)操作数在使用之前被转换为普通整型,即为整型提升(对与补码而言)。

        3.2补码的一些理解

4.浮点数在内存中的存储

        4.1先看一段代码:

        4.2浮点数存储过程


1.整数在内存中的存储

整数的二进制表示形式有三种:原码,反码和补码。

这三种表示方式均由符号位数值位两部分组成,符号位为最高位,0为”正“,1为”负“,其余位均为数值位。

原码:直接将数值按照正负数翻译成二进制数得到的就是原码;(人的思维上所想的数)

反码:原码符号位不变,其他位按位取反;(0变1,1变0)

补码:反码基础上+1;

正整数的原码,反码,补码都相同。

而负整数的原码,反码,补码都不相同;

//5的原,反,补码:
原:0000000000000000000000000000101
反:0000000000000000000000000000101
补:0000000000000000000000000000101

//-5的原,反,补码:
原:1000000000000000000000000000101
反:1111111111111111111111111111010
补:1111111111111111111111111111011

那为什么有原码了还要有反码,补码呢?

这是因为在计算机系统中,数值都是用补码形式来表示储存的。

那为什么是补码形式呢?

原因是使用补码可以将符号位和数值域统一处理;加减法也可以同一处理(cpu只有加法器);原码反码相互转换其运算过程也是相同的(均为取反加一),不需要额外的硬件电路。

2.大小端字节存储和字节序判断

2.1何为大小端?

当一个数值的内存大小超过一个字节时,就得考虑一个问题,既然地址分高低,那我数值的低位存在内存的低地址,还是存在内存的高地址中?于是就有了存储顺序的问题,按照不同的存储顺序,将其分为大端字节序存储小端字节序存储

大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。(低位存高址)

小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。(低位存低址)

为什么会有大小端之分?

历史原因:在计算机发展的早期阶段,不同的处理器厂商采用了不同的字节序,导致在数据交换和通信时出现了问题。为了解决这个问题,需要有大小端之分。

数据交换:在进行数据交换或通信时,如果双方的字节序不一致,就需要进行字节序的转换,否则会导致数据解析错误。

硬件兼容:不同的硬件架构可能采用不同的字节序,为了保证软件在不同平台上的兼容性,需要考虑大小端问题。

总之,大小端之分主要是为了解决不同处理器和硬件架构之间的兼容性和数据交换的问题。

2.2设计程序判断机器的字节序存储方式

//方法1
#include 

int check_sys(void)
{
	int i = 1;
	return (*(char*)&i);
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}
//方法二,运用联合体的特性
#include 

int check_sys(void)
{
	union
	{
		int i;
		char c;
	}un;
	un.i = 1;
	return un.c;
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

 对于联合体的方法暂时不理解的可以看我下一章,会详细讲解联合体。

3.整型提升与补码的深层含义

3.1    在c语言中,整型算术运算总是至少以缺省整型类型(int / unsigned int)的精度来进行的。为了获得这个精度表达式中字符型(char)和短整型(short)操作数在使用之前被转换为普通整型,即为整型提升(对与补码而言)。

double,float(先提升为double),long long运算时为8个字节。

如何整型提升?

a. 在补码的基础上,有符号补符号位

b.无符号补0

#include 

int main()
{
	unsigned char a = 100;
	unsigned char b = 200;
	unsigned char c = a + b;
	printf("%d %d\n", a + b, c);

	return 0;
}

大家可以自己先思考一下,答案会是什么?

 关于整数和浮点数在内存中的存储_第1张图片

 接下来进行分析:

首先unsigned char类型所能表示的数值范围是0~2^8-1,即为0~255(二进制:11111111),那么a与b没有超出取值范围,所以第一个打印的是300(100 + 200);再来看变量c,变量a和b的和300赋值给变量c,而变量c取值范围容不下300,那就要发生截断:

//300
原码:000100101100
反码:000100101100
补码:000100101100
//c为char类型,8个比特位
发生截断:00101100(补码)--44

关于整数和浮点数在内存中的存储_第2张图片

 关于整数和浮点数在内存中的存储_第3张图片

 细心的小伙伴会发现,44 + 256 = 300,这是巧合吗?

3.2补码的一些理解

补码的局限性:补码只能表示有限的数值范围,对于超出范围的数值,计算机会出现溢出错误。此外,补码也无法表示负零和无穷大等特殊数值。

数据是以二进制补码进行存储和运算的,不同数据类型所能表示的范围不一样,举个例子:

signed char:-128~127;unsigned char:0~255;

对于signed char是由符号的,所以二进制最高位得留给符号位,当最符号位为0时(+),所能表示范围为0~127(2^7 - 1)包括0在内共128个数;当最高位为1时(-),理论上来说应该是-0~-127同样128个数,但是0就是0,无正负之分,既然0已经划分到0~127去了,负数范围为了也能同样表示128个数值,那就变成了-1~-128,总体上就为-128~127。

对于unsigned char是无符号类型的,自然最高位就不用给符号位留位子,那取值范围为0~255(2^8 - 1).

那补码要如何有规则地处理这些数据呢?这里可以把它看成一个时钟,亦是一个轮回。

还是以signed char为例:

由整数0开始(补码00000000)直到127(01111111),127加一是不是就是128了?其实不是,但是有联系,128补码为10000000,但是是signed char类型,所以最高位的1会被当成符号位表示负数,那是不是表示0呢?也不是,因为这是负数的补码,要转换成原码才找到它表示多少,10000000取反加一,得到00000000,跟上面解释的一样不存在-0这种表示形式,那么这里的00000000被表示为-128,-128加一-127……直到-1加一变为0,0加一等于1……,不断循环往复,不断进行轮回。

关于整数和浮点数在内存中的存储_第4张图片

4.浮点数在内存中的存储

4.1先看一段代码:

#include 

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;
}

运行结果为关于整数和浮点数在内存中的存储_第5张图片

 根据代码运行结果,小伙伴可能会发生这样的疑惑:当n为整型时,将其整型指针强转为浮点型指针,使指针指向为浮点型,解引用时结果为什么会和整型不一样?当n为浮点型时,结果为何又有差异?有些小伙伴可能已经想到了,既然值不同,那就说明其在内存中存储的形式不一样。

想法是正确的,浮点数的存储方式与整型的存储方式的确不同。

整型的存储方式前面已经讲过了(忘了的快回头!),现在讲讲浮点数的存储方式:

根据IEEE754(电气电子工程师学会)规定,任意一个二进制浮点数V可以表示成下面的形式:

V = (-1)^S * M * 2^E

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

M表示有效数字,M是大于1小于2的

2^E表示指数位

举个例子,十进制的6.0,二进制为110.0,根据上面规则即为110.0=(-1)^0 * 1.10 * 2^2

S=0;M=1.10;E=2.

十进制的-6,二进制为-110.0,即为-110.0=(-1)^1 * 1.10 * 2^2

S=1;M=1.10;E=2

既然任意浮点数能用这三个变量表示,那么内存中存放这三个变量的信息不就行了?没错,是这样的。

IEEE754规定:

对于32位的浮点数,最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M

 

对于64位的浮点数,最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

4.2浮点数存储过程

IEEE 754 对有效数字M和指数E,还有一些特别规定。
前面说过, 1 M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数 部分。 IEEE 754 规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存更多的有效数字。
至于指数E,情况就比较复杂 ,首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须 保存成 10+127P(中间值)=137,即10001001。
4.3浮点数取出过程
E不为全0或者不为全1:
E的计算值减去127得到计算值,再将有效值M加上第一位的1;
E为全0:
这时的E等于1-127(中间值)或者1-1023(中间值)即为真实值,有效数字M不再加1,而是还原为0.xxxxxx的小数,这样做是为了表示+-0,即无穷小值。(乘上一个2^(-127)你就说小不小)。
E为全1:
这是,如果有效数字M全为0,表示+-无穷大值(正负取决于s)。

回到前面那段代码:

关于整数和浮点数在内存中的存储_第6张图片 9在整型中二进制补码存储为00000000000000000000000000001001

将00000000000000000000000000001001拆分为浮点数二进制补码存储形式:
0  00000000  00000000000000000001001  

S=0;E=1-127;M=00000000000000000001001;

显然,E为全0,表示一个很小的正数;

看第二环节,将n以浮点数二进制存储9.0时,即S=0;M=1.001;E=3;

那么内存表示为0  10000010  00100000000000000000000

即为01000001000100000000000000000000

上面二进制数被当作整型时表示

关于整数和浮点数在内存中的存储_第7张图片

以上就是这一期的全部内容,如果对你有所帮助的话,不要忘记点赞,收藏加关注,如果文章存在错误或者需要补充,欢迎评论区留言,我们下期再见!!!

你可能感兴趣的:(算法,数据结构,c语言,c++)