在操作负中,我们讲解过整形提升运算符(详情请看写文章-CSDN创作中心操作符(原码反码补码)-CSDN博客写文章-CSDN创作中心),知道CPU都是基于整形运算的,而且每个类型都有其最大存储的整数。
目录
字符型在内存中的存储:
浮点数在内存中的存储:
E全为0时:
E全为1时:
举例:
总结:
字符型在内存中的存储:
比如一个char类型,我们知道在内存中占据1个字节,而且分为有符号和无符号类型。当是有符号的时候,最高位被符号位占据,所以一个字节能存储的最大整形就是2^7-1 = 127;最小整数是 -128。这个-128即0111 1111 + 1得到的。
我们定义char类型补码中存储的10000000是-128。
int main()
{
char a = -128;
//先整形提升,保留符号位
//之后打印无符号数
printf("%u\n", a);
return 0;
}
前面的位按照符号位补全。C语言中,整形算数运算符总是至少以缺省整形类型的精度来进行的,为了获取这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整形,这种转换称为整形提升。
通用CPU是难以直接实现两个8比特字节直接相加运算的。所以表达式中各种长度可能小于int长度的整形值,都必须先转换为int或unsigned int,然后才能送入CPU执行运算。
我们再举一个例子:
int main()
{
//char类型取值范围是:-128~127
//char是占用1个字节的,1个字节8个bit位
char c1 = 125;
//00000000 00000000 00000000 01111101
//发生截断
//01111101 - c1
char c2 = 10;
//00000000 00000000 00000000 00001010
//00001010 - c2
char c3 = c1 + c2;
//00000000 00000000 00000000 01111101
//00000000 00000000 00000000 00001010
//00000000 00000000 00000000 10000111
//10000111 - c3
//提升
//11111111 11111111 11111111 10000111 - 补码
//11111111 11111111 11111111 10000110 - 反码
//10000000 00000000 00000000 01111001 - 原码
//-121
printf("%d\n", c3);
//%d 打印有符号整数
//%u 打印十进制无符号整数
return 0;
}
我们再举一个例子:
int main()
{
char a = -1;
//10000000 00000000 00000000 00000001
//11111111 11111111 11111111 11111110
//11111111 11111111 11111111 11111111
//存储在a中发生截断
//11111111 - a
//11111111 11111111 11111111 11111111 - 提升
//10000000 00000000 00000000 00000000
//10000000 00000000 00000000 00000001
signed char b = -1;
//11111111 - b
unsigned char c = -1;
//11111111 - c
//无符号整形提升,最高位补0
//00000000 00000000 00000000 1111111
printf("a = %d, b = %d, c = %d\n", a, b, c);
//%d - 十进制的形式打印有符号的整形
return 0;
}
C语言规定char类型默认是否带有正负号,有当前系统决定。整形提升时,最高位默认为符号位,也是说不够32位,前面就补上符号位。
int main()
{
char a = -128;
char b = 128;//和上面在内存中的存储一样
//此时char最大存127,再打二进制就加相差多少的数
//01111111+1=10000000
//打印无符号的数就先整形提升之后打印
printf("%u\n", a);
return 0;
}
我们再来看一个例子:
int main()
{
char a[1000];
//-128~127
//-1 -2 -3 ... -128 127 126 125 ... 5 4 3 2 1 0 -1 -2 ... -128 127 5 4 3 2 1
//128 + 127 = 255
int i = 0;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d\n", strlen(a));//求字符串长度找的是\0,\0的ASCII码值是0,其实找的就是0
//255
return 0;
}
总结:对于有符号的char我们可以看成一个圆。
总结:
其实每个类型都会有这样的一个循环,我们学习内存的存储是为了从底层理解,这样即使出现很奇怪的数字也可以理解是什么原因。
浮点数在内存中的存储:
为什么有双精度浮点数和单精度浮点数?难道只是内存占据大小不一样吗?
IEEE754规定:
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数v可以表示成以下形式:(-1)^S*M*2^E。
-
(-1)^S表示符号位,当s=0,v为正数;当s=1,v为负数。
-
M表示有效数字,大于等于1,小于2。
-
2^E表示指数位。(如取9)指数位就是小数点向前移动了3位,所以E为3。
IEEE 754规定:对于32位的浮点数,最高的一位是符号位S,接着的8位是指数E,剩下的23位为有效数字M(float)。
对于64位的双精度浮点数,最高的一位是符号位S,接着的11位是指数E,剩下的52位为有效数字M(double)。
这意味着,如果E为8位,他的取值范围为0~255;如果E为11位,它的取值范围为0~2047,但是我们知道,科学计数法中E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如2^10的E是10,所以保存32位浮点数时,必须保存成10+127=137,及10001001。
对于进制的转换,可以去看这篇文章进制的转换-CSDN博客。
既然M是大于1小于2的,那么第一位肯定是1,以至于我们在保存数据时,是不是就可以把第一位的1省略?确实是这样。
IEEE 754规定:在计算机内部保存M时,默认第一位数总是1,因此可以被舍去,只保存后面的部分。如保存1.01时,有效位第一个不计入内存,默认是1,等到读取时,再把第一位的1加上去。这样做的目的是节省一位有效数字。以32位单精度浮点数为例,留给M只有23位,将第一位的1舍去后,就等于可以保存24位有效数字。
int main()
{
float f = 5.5;
//5.5
//101.1二进制
//(-1)*0*1.011*2^2
//S=0
//M=1.011
//E=2
//之后E加上127为129
//0 10000001 01100000000000000000000
//0100 0000 1011 0000 0000 0000 0000 0000
//在内存中是0x40b00000
printf("%lf", f);
return 0;
}
E在计入内存时,加上了127,在读取时就减去127得到真实值;M也是计入内存时前面去掉了1,在读取时(就是带入公式计算时)前面就加上了1。
E全为0时:
这时,读取时,浮点数的指数E就等于1-127(或者1-1023)就等于真实值,有效数字M前不再加上1,而是还原为0.xxxx的小数。这样是为了表示+-0,或者无限接近于0的数。
此时的数字没有意思,我们不做讨论。
E全为1时:
这时直接就是无穷大,不讨论。
举例:
我们举例来看浮点类型在内存中的存储:
int main()
{
int n = 9;
//00000000000000000000000000001001-补码
float* pfloat = (float*)&n;
printf("n值为%d\n", n);
printf("*pfloat值为%f\n", *pfloat);
//0 00000000 00000000000000000001001
//因为E为全0所以
//(-1)^0*0.00000000000000000001001*2^-126
//趋近于0
*pfloat = 9.0;
//1001.0
//1.001*2^3
//(-1)^0*1.001*2^3
//0 10000010 00100000000000000000000
//内存中存的是
//打印的是整形,所以很大
printf("n值为%d\n", n);
printf("*pfloat值为%f\n", *pfloat);
return 0;
}
总结:
double类型的精度比float精度更高。
因为对于小数,有时候会一直乘二进制拼凑成其十进制的小数,可能会超出M的极限,所以会近似取值,就是对于有些小数无法精确保存。
所以两个浮点数比较大小时,直接使用 == 可能存在问题