整数与浮点数在内存中的存储
- 一,大小端存储
- 二,整数在内存中的存储
- 三,浮点数在内存中的存储
- 3.1浮点数的存储规则
- 3.2浮点数的存储过程
- 3.2.1有效数字M
- 3.2.2指数E
- 3.2.3浮点数存储的特殊情况
- 4,例题讲解
在C语言的编程中,我们常常创建整型与浮点型的变量,这里我们介绍内存中它们是如何进行转换和存储的。 文章中展示的例子是在32位系统下使用VS2022进行操作的结果,不同软件可能会有大小端的区别。
首先介绍一下大小端存储及区别
一,大小端存储
大小端存储指的是对于多字节数据的表示方式,即字节序。
大端存储(Big Endian):高位字节存储在内存的低地址处,低位字节存储在内存的高地址处;
小端存储(Little Endian):低位字节存储在内存的低地址处,高位字节存储在内存的高地址处
例如:
int a=0x11223344;我们以16进制的方式创建一个变量a,在大端存储中,a的存储方式应该为0x11 22 33 44(从左到右地址逐渐递递增)在小端存储中,a的存储方式应该为0x44 33 22 11(从左到右地址逐渐递递增)
在实际应用中,不同的体系结构和处理器可能采用不同的字节序。但无论是大端存储还是小端存储,在将变量从内存中拿出使用时都会将其还原成正确的形式。
二,整数在内存中的存储
在现代的计算机中主要采用的数字集成电路完成,数字电路通过高低电平只能表示0和1,所以就出现了,计算机只会识别0和1。无论是存储还是计算,计算机均采用二进制体系完成。
在C语言中不同数据类型的大小不一样,如int类型每个占4个字节,而每个字节又等于8个比特位。
sizeof(int)=1byte=8bit每个比特位存储的便是0或1。
一个int类型共有32个比特位,每个比特位只能是0或1,而我们又知道,整型包括有符号整型和无符号整型,无符号整型只有正整数,范围为0~232-1,而有符号整型既有正整数又有负整数,范围为 -231 ~231-1
那为什么会存在正负数之分?操作系统又是如何分辨正负数的?
这就需要了解关于原码反码补码的知识了。
十进制数在计算机中通常不是直接储存的,而是先转化位二进制数在进行存储。而为了区分正负数,计算机对有符号整数采用了不同的编码储存方式,也就是源码,反码,补码。
我们分开讨论正负整数的存储:
正整数:正整数的源码=反码=补码
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
int a=5;
a的源码=反码=补码为:0000 0000 0000 0000 0000 0000 0000 0101
int b=-5;
b的源码为:1000 0000 0000 0000 0000 0000 0000 0101反码为:1111 1111 1111 1111 1111 1111 1111 1010补码为:1111 1111 1111 1111 1111 1111 1111 1011
而计算机之所以会有三种储存整形的方式的原因都是因为计算的难易,
源码的一个缺点是存在两种不同的表示方式来表示0(0000 和 1000),这可能导致一些不必要的复杂性。
反码的一个优点是消除了0的两种表示,但缺点是进行加减运算时相对复杂。
补码是最常用的表示法,特别是在现代计算机中。补码的一个主要优点是它使得加法运算更加简单,因为对于任何整数x,都有 x + (-x) = 0,这在补码表示下总是成立。另一个优点是只有一个0的表示(0000),这使得处理更加简单。使用补码,可以将符号位和数值域统⼀处理
对于整形来说:数据存放内存中其实存放的是补码。
三,浮点数在内存中的存储
3.1浮点数的存储规则
C语言中的浮点数类型包括: float、double、long double 类型。
计算机读取浮点型和整型的方式是不一样的,我们先来看一段代码:
#include <stdio.h>
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;
}
结果为:
而造成这样的原因,就是因为计算机读取存储的浮点数和整型的方式是不一样的。
根据国际标准IEEE(电气和电子⼯程协会)754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
V = (−1) S ∗ M ∗ 2E
• (−1)S 表示符号位,当S=0,V为正数;当S=1,V为负数
• M表示有效数字,M是大于等于1,小于2的
• 2E 表示指数位
了解上面的知识后,我们还需知道,在二进制中小数点后的数字是如何划算的,
例如:
5.5,写成二进制表示为101.1,在转换成科学计数法为1.01×103
小数点前就是正常的二进制转十进制从右往左的单位依次为20,21,22……,小数点后的第一个1表示2-1,然后往后若存在数,依次为2-2,2-3……,
所以二进制101.1划算为十进制的算式就为:
**1×22+0×21+1×20+1×2-1=5.5,
了解之后,再结合上面的国际标准中计算机存储浮点数的方式来介绍计算机中的浮点数存储。
例:
⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2
(5.5相当于1.011×23)
那么,按照上⾯V的格式,可以得出S=0,M=1.01,E=2。
⼗进制的-5.0,写成⼆进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
当然,32位机器和64位机器在存储浮点数上也有细微的差别。
IEEE 754规定:
对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M.
对于64位的浮点数,最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。
32位浮点数(float):
64位浮点数(double):
所以,浮点数的存储其实就是存储S,E,M相关的值。
3.2浮点数的存储过程
IEEE 754 对有效数字M和指数E,还有⼀些特别规定。
3.2.1有效数字M
前⾯说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。
例如:
5.5为101.1==>1.011×22
0.5为0.1==>1.0×2-1
这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字。
3.2.2指数E
首先,我们要了解,指数E为一个无符号整数(unsigned int)。所以当存储的为32位浮点数时,E的取值范围为0~255;存储64位浮点数时,E的范围为0 ~2047。
但是,我们知道,科学计数法中是存在负数的。
所以IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。⽐如,210的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
综合来说,这当中的E的存储存在三种情况:
一,E不全为0或不全为1
这时,E的存储就为指数加上中间值,例如:存储32位浮点数0.5
0.5的二进制数为0.1,按照IEEE 754的规则转换,为正数S=0,有效数字M要大于等于1并且小于2,所以小数点右移一位,M=1.0,E=-1,
0.5= (−1) 0 ∗ 1.0 ∗ 2-1
存储时,为正数,符号位(S)为0,阶码(E)-1+127(中间值)=126,转化为二进制01111110。将二进制存入E的八位中。尾数(M)1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其⼆进制表示形式为:
0(S) 01111110(E) 00000000000000000000000(M)
二,E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,此时的数无限接近于0,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
0 00000000 00100000000000000000000≈0
三,E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
0 11111111 00010000000000000000000≈+∞
3.2.3浮点数存储的特殊情况
在浮点数存储的时候,因为小数点后的数依次表示2-x逐渐递减,所以有些数是不能精确存储的,例如:
#include <stdio.h>
int main()
{float a = 0.4554f;printf("%.10f ", a);return 0;
}
出现这种情况,计算机会根据两数之间的差的绝对值小于一个很小的范围来存储一个十分接近的数,双精度比单精度浮点数会更加接近实际的值。
4,例题讲解
了解完浮点数的存储规则后我们再来看下开始的例题:
#include <stdio.h>
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转化为二进制补码存入内存:
因为正整数的原码,反码,补码相等,所以n的补码就为:
00000000 00000000 00000000 00001001
当我们以整型形式打印时,就是正常打印,
但是,当我们以32位浮点数的形式打印时,计算机就会将补码以浮点数的形式读取
0(S) 00000000(E) 00000000000000000001001(M)
符号位为0,表示为正数,又因为E全为0,按照上面介绍的情况,此时的指数为1-127,非常小,无限接近于0,也就是小数点后有效数字出现的位数很小,所以我们以浮点数形式打印时,显示的位数有限,所以近似取0;
————————————————————
我们再来看下以浮点数形式存入9.0;
此时浮点数按照规定的规则存入内存(9.0=>1001.0=>1.001×23),二进制码为:
0(S) 10000010(E) 00100000000000000000000(M)
当我们以浮点数形式打印时,就是正常打印,
但是,当我们以整型的形式打印时,计算机就会将二进制码以整型的形式读取,
01000001 00010000 00000000 00000000
按照整型的形式读取,该数就为一个很大的正数,为:
230+224+220,我们使用计算器计算一下:
,所以
这就是打印的结果。