1.整型的存储
一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
例如:
int a=20; int b=10;
分别为a和b开辟4个字节大小的空间,那在计算机内存中如何对他们进行存储?
计算机中的整数有三种表示方法,即原码、反码和补码
三种表示方法均有
符号位
和
数值位
两部分,符号位都是用
0
表示
“
正
”
,用
1
表示
“
负
”
,而正
负整数的原反补码的表示方法有所不同。
对于负数:
原码
直接将某个正负数按照二进制的形式翻译就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1
对于正数:
正数的原、反、补码都相同,求得原码即可求的反和补码;
对于整形来说:数据存放内存中其实存放的是补码。
原因是因为:
使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器
)计算机不做减法。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
上图是基于VS2019,展示整型数在内存中的存储。可以看到,内存中是以16进制补码的方式进行的存储。但是,在存储方式有些不同。这涉及到大小端存储的问题。
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
中;小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,
,保存在内存的高地址中。通常情况下,可认为逆序存储为小端字节序,而正序存储为大端字节序。例如:
例题 1:
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
//char型的取值范围是-128~127;
//所以而整数a=10000000 00000000 00000000 10000000 原码
//11111111 11111111 11111111 01111111 反码
//11111111 11111111 11111111 10000000 补码
//将整数a存储在char型中,要发生截断,即
//1000000 -a
//%u表示无符号整型,而从char型到整型需要进行整型提升
//整型提升按照该数本身是有还是无符号进行提升,该数有符号数,则提升符号位,该数无符号,提升0
//10000000是有符号数,第一位是符号位,所以提升为11111111 11111111 11111111 10000000
//将该数按照无符号整型对待,所以,直接将其当成原码进行打印;
//该二进制数转化为10进制后,结果为4294967168
例2:
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
//char型的取值范围是-128~127;char本身表示不了128,就算放进去,也不会是128;
//所以而整数a=00000000 00000000 00000000 10000000 原码、反码和补码相同
//将整数a存储在char型中,要发生截断,即
//1000000 -a
//%u表示无符号整型,而从char型到整型需要进行整型提升
//整型提升按照该数本身是有还是无符号进行提升,该数有符号数,则提升符号位,该数无符号,提升0
//10000000是有符号数,第一位是符号位,所以提升为11111111 11111111 11111111 10000000
//将该数按照无符号整型对待,所以,直接将其当成原码进行打印;
//该二进制数转化为10进制后,结果为4294967168
2.浮点型数据的存储
根据国际标准
IEEE
(电气和电子工程协会)
754
,任意一个二进制浮点数
V可以表示成下面的形式:
(-1)^S * M * 2^E
其中,(-1)^S相当于是符号位,当浮点数为正数,s=0;反之,S=1;
M相当于有效数字,M>=1&&M<2;
E为指数位,相当于E表示移动了几位,向左移动为正数,向右为负数,例如:
十进制的5.5,写成二进制是 101.1 ,相当于 (-1)^0 * 1.011 * 2^2
那么,按照上面的格式,可以得出s = 0,M = 1.011,E = 2
那么,按照上面的格式,可以得出s = 0,M = 1.011,E = 2
十进制的 - 5.0,写成二进制是 - 101.0 ,相当于(-1)^1 * 1.01 * 2 ^ 2 ,那么s = 1,M = 1.01,E = 2
0.5(1 / 2)的二进制形式为0.1,由于规定有效数部分必须为大于等于1的数,即将小数点右移1位,则为(-1)^0 * 1.0 * 2 ^ (-1),那么s=0,M=1.0,E=(-1);
对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M
;
对于
64
位的浮点数,最高的
1
位是符号位S,接着的
11
位是指数
E
,剩下的
52
位为有效数字
M
。
IEEE 754
对有效数字
M和E
还有一些特别规定。
首先,对于M来说,
1≤M<2
,也就是说,
M
可以写成
1.xxxxxx
的形式,其中
xxxxxx
表示小数部分。IEEE 754规定,在计算机内部保存
M
时,默认这个数的第一位总是
1
,因此可以被舍去,只保存后面的xxxxxx部分。比如保存
1.01
的时候,只保存01
,等到读取的时候,再把第一位的
1
加上去。这样做的目的,是节省
1
位有效数字。
至于指数
E
,情况就比较复杂。
首先,
E
为一个无符号整数(
unsigned int
)
这意味着,如果
E
为
8
位,它的取值范围为
0~255
;如果
E
为
11
位,它的取值范围为
0~2047
。但是,科学计数E
是可以出现负数的,所以IEEE 754
规定,存入内存时
E
的真实值必须再加上一个中间数,确保其一定会成为无符号正整数,对于
8bite
位的
E,这个中间数
是
127
;对于
11
位的
E
,这个中间数是1023
。比如,
2^10
的
E
是
10
,所以保存成
32
位浮点数即E为8位时,必须保存成
10+127=137,即10001001
。
1.
E
不全为
0
或不全为
1
0.5
(
1/2
)的二进制形式为
0.1
,由于规定正数部分必须为
1
,即将小数点右移
1
位,则为
(-1)^0 * 1.0 * 2^(-1)
,E为
-1+127=126
,表示为01111110,而尾数
1.0
去掉整数部分为
0
,补齐
0
到
23
位
00000000000000000000000
,用它表示M位。所以,最终0.5的二进制表示形式为:
0 01111110 00000000000000000000000
2.E
全为
0
这时,浮点数的指数
E
等于
1-127
(或者
1-1023
)即为真实值,有效数字M
不再加上第一位的
1
,而是还原为
0.xxxxxx
的小数。这样做是为了表示
±0
,以及接近于
0
的很小的数字。
举例:
int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n);//9 printf("*pFloat的值为:%f\n",*pFloat);//0.000000 *pFloat = 9.0; printf("num的值为:%d\n",n);//1091567616 printf("*pFloat的值为:%f\n",*pFloat);//9.000000 return 0; }
首先,求%d,可根据题目直接写出n=9;而第二问中,求将&n强制类型转换成float型后,用浮点数表示。首先,int n=9的二进制表示为:0000 0000 0000 0000 0000 0000 0000 1001,则在强制类型转换后将该二进制直接认为是浮点形式的存储,S=0,E=00000000,M=00000000000000000001001,所以此时的浮点形式表示为(-1)^0*0.00000000000000000001001×2^(-126),用十进制小数表示就是0.000000。
第三问中,浮点数9.0的表示形式为1001.0,所以(-1)^0*1.001*2^3,所以,S=0,E=3+127=130,写成二进制为10000010,M=1.001。所以浮点数9.0整体写为二进制形式为:
0 10000010 001 0000 0000 0000 0000 0000,题目中要求该数的%d,所以将其当做整型看待,其整型数为1091567616 。
第四问,求浮点数的浮点表示,直接得出9.000000