整型和浮点型在内存中的存储
整型的存储
整型家族的表示范围:
limits.h
计算机中的整数有三种表示方法,即原码、反码和补码。
而在计算机内存中存储的是 补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位负整数的三种表示方法各不相同。
符号位就是二进制码的第一位。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
表现方式
正整数的原码、反码和补码相同。
负整数就需要先转化为反码,再转化为补码。
1(int类型)
00000000000000000000000000000001 (原码)
00000000000000000000000000000001 (反码)
00000000000000000000000000000001 (补码)
正整数的原码、反码和补码相同
-1(int类型)
10000000000000000000000000000001 (原码)
11111111111111111111111111111110 (反码)
11111111111111111111111111111111 (补码)
负整数就需要先转化为反码,再转化为补码。
我们来看看这段代码
#include <stdio.h>
int main() {
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a = %d , b = %d , c = %d", a, b, c);
return 0;
}
输出结果为:
为什么我们的 c =255
,而不是 -1 呢
首先 c
是一个无符号的字符(char)类型,所以它的原码应该是 10000001
,补码为 11111111
,而我们用占位符%d
打印时,会将我们的数据进行整型提升,而我们的c
是 unsigned char(无符号的字符类型)
,所以我们进行整型提升时,高位应该补0,因此补码为00000000000000000000000011111111
, 换算为十进制就是255
.
再看一题
#include <stdio.h>
int main() {
char a = -128;
printf("a = %u", a);//注意这里是 %u(无符号整型输出)
return 0;
}
输出结果:
解析:
我们要先求出它在的补码
a的原码为10000000000000000000000010000000
,补码为11111111111111111111111110000000
,而现在我们只取求出补码的后八位,因为 a
是char类型所以需要截断后八位.得到补码 10000000
.
而我们要输出的是 %u,而我们的a又是char类型,所以高位补 1 ,得 11111111111111111111111110000000
,而这个数换算为十进制就是 4294967168.
分析这段代码
#include <stdio.h>
#include <string.h>
int main() {
char a[1000] = {0};
for (int i = 0; i < 1000; i++) {
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
输出结果:
解析:
有符号的char类型的取值范围为-128~127
,无符号的为:0~255
有符号char的取值范围的由来:
以下为二进制码
00000001 (1)
00000010 (2)
00000011 (3)
…
01111110 (126)
01111111 (127)
10000000 (-128)
10000001 (-127)
…
11111110 (-2)
11111111 (-1)
因为是char类型,只有八个比特位,如果这里再进 1, 就会变成
100000000
,所以需要抛弃越界的比特位.00000000 (0)
00000001 (1)
由此可见,其实是一个循环
而strlen
求取的长度是以 \0
为标志的,而 \0
的本质就是 0 ;所以在找到0的时候就会停止,而这个数从-1开始,正好有255个字节.
浮点型的存储
浮点数的表示范围:
float.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;
}
大家可以思考一下,为什么以上代码的输入结果为何如此.
其实也正好说明了浮点数和整数在内存中存储的方式是不同的,现在我们一起来学习float的存储方式吧.
浮点数存储规则
浮点数没有原码,反码,补码
根据国际标准IEEE(电气和电子工程协会)754规定,任意的一个二进制浮点数V,可以这样表示:
- (-1)^S * M * 2^E
- (-1)^S表示符号位,但S = 0,V为正数,当S=1,V为负数;
- M表示有效数字, 1<M<2;
- 2^E表示指数位
在32位的浮点数(float)中
最高位 也就是第一位是符号位 S ,接着的8位是指数E,剩下的23位为有效数字M
对于64位的浮点数,最高的1位也是符号位S,接着的11位是指数E,剩下的52位为有效数字M
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舍去以后,等于可以保存24位有效数字。
至于指数 E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,取值范围是0~255, 如果E是11位,它的取值范围是0~2047,如果科学计数法中的E是负数的话,存入内存时的E就需要加上一个中间值,对于8位(float)的E来说,这个中间值是127,对于11位(double)的E,这个中间值是1023
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,但这个值已经无限接近于0
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
当我们学完这一些就该回来看一下我们之前的题目了
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=9时,二进制码为,0000000000000000000000000001001
而当用浮点数的方法提取出来时 .S = 0 ,M = 00000000000000000001001, E=-126(因为E存储的空间为全零,所以是1-127=-126)
-1^0 * 1.00000000000000000001001 * 2 ^-126
此值无限接近于零,而且打印时,只显示前六位小数,所以结果为 0.000000
.
当我们使用 *pFloat = 9.0
时,它的二进制码就改变了, 9.0
的二进制数位1001.0
转化为科学计数法为,1.001*2^3
此时S=0;
M=00100000000000000000000
E=3+127 = 130
完整的二进制码为:0 10000010 00100000000000000000000
如果我们使用%f打印,或是浮点型的输出占位符,输出结果就是9.000000
但如果使用整型的方式输出,它就会按整型的方式提取结尾也就成为了1091567616
0.000000`.
当我们使用 *pFloat = 9.0
时,它的二进制码就改变了, 9.0
的二进制数位1001.0
转化为科学计数法为,1.001*2^3
此时S=0;
M=00100000000000000000000
E=3+127 = 130
完整的二进制码为:0 10000010 00100000000000000000000
如果我们使用%f打印,或是浮点型的输出占位符,输出结果就是9.000000
但如果使用整型的方式输出,它就会按整型的方式提取结尾也就成为了1091567616
本次分享到此结束,感谢大家的观看,若有错误,欢迎大家指正.