C语言数据的存储方式(整型和浮点型)

1.整型的储存形式:原码反码补码

一个整数的二进制表示方式有3种。分别是源码,反码和补码。

  1. 源码是数字本身的二进制序列。例如4的源码就是0100.
  2. 反码是对源码的除符号位外(序列的第一位是符号位,0表示整数,1表示负数的每一个二进制位进行按位取反。(0变成1,1变成0)
  3. 补码是对反码的二进制序列+1

由于正数(无符号数)的符号位是0,所以这里有一个结论。正数的源码,反码,补码相同

例子1:
写出4的源码,反码和补码。

分析:由于4是正数,所以4的源码反码和补码相同。4的源码就是它的二进制序列。如下图
在这里插入图片描述
例子2:
写出-1的源码,反码和补码

分析:-1不是正数,不能用上面的结论了。

由于-1是负数,所以高位符号位是1。因此源码如图。
反码是源码除符号位外的每一位按位取反,所以反码如图。
补码是反码的二进制序列+1,所以补码如图。
在这里插入图片描述

#注意事项

注1:在计算机中,整型的存放是以补码的形式来存放的。当你想要使用计算机内存的特定整型时,它会从补码自动转换成源码供你使用。

举两个例子来说明:

int main()
{
	int a = -1;
	return 0;
}

在内存中你觉得a的值是什么呢?
答案是
在这里插入图片描述
f是16进制,换成二进制就是1111。8个f正好是32个1。这个例子说明了整型是以补码在内存中储存的。


在下面这段代码,毫无疑问会打印-1。
这说明了整型在拿出来的时候会打印它的源码。

int main()
{
	int a = -1;
	printf("%d", a);
	return 0;
}

1.1以补码存放的好处

  1. 在最高位是符号位的前提下,补码可以解决加减法的问题。用源码是无法计算减法的。(自己可以举个例子试一下)
  2. 补码和源码相互转换的方式其实是一致的。
    补码换成源码可以通过先减一再取反的方式得到源码,也可以通过先取反再加一的方式得到源码。
    计算机是用先取反再加一的方式来用补码得到源码的。这样可以保证二者的方式一样,不需要额外的电路硬件

1.2大小端字节序

先说明大小端的定义:

1.大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
2.小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

在每个不同的平台上,存储方式都不同。

简单来说:大端字节序是顺序存储。小端字节序是逆序存储。
举例子说明:

int main()
{
	int a = 1;
	return 0;
}

这是a的值。但道理a应该是00 00 00 01才对呀?为什么它是01 00 00 00呢?

这证明了vs是用小端字节序存储的。
在这里插入图片描述
需要说明的是:字节的高位是第一位。1的补码是00 00 00 01。则高位是00。
我们画图来进一步说明:
在这里插入图片描述
需要说明的是:字节的高位是第一位。1的补码是00 00 00 01。则高位是00。上面这个图表示的就是字节的低位存储在低地址处。属于小端储存方式。

在这里插入图片描述
上面这个图表示的就是字节的高位存储在高地址处。属于大端储存方式。

1.2.1 如何验证这个平台是大端字节序还是小端字节序呢?

代码如下:
思路就是举一个例子。如果是小端存储则返回的是1,不是则返回0.

int check_sys()
{
	int a = 1;
	return *(char*)&a;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
	return 0;
}

1.3关于整型数据存储方式的练习题

根据整型储存知识操作符的知识,我们可以做一些综合的练习题了。

习题1:这段代码输出什么?

1.
#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;
}

答案是:-1,-1,255.
这道题结合了整型提升的知识。

-1的补码是1111 1111。由于a,b,c都是char类型的。而打印的时候是用有符号整型来打印的(%d)。则需要进行整型提升。

整型提升的方法为:补最高位的符号位。若最高位为1,则前面补1,直至补齐32位。若最高位为0,则前面补0。(无符号数也补0)

a和b由于是有符号数。最高位是1,则前面补1。
对应的补码为32位1,对应的原码是-1。因此打印-1。

c由于是无符号数,没有符号位,则补0。
对应的补码是11111111,由于c是无符号数,原反补码相同,所以原码也是11111111,打印出来是255。

注意:任何补位操作的对象都是补码,不要对着原码咔咔咔地算个不停。切记要先换成补码。计算机是不认得原码的!!!!!!

附上图解:
在这里插入图片描述
在这里插入图片描述
习题2:
打印什么呢?

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}

答案:4,294,967,168

这道题还是用了整型提升。
-128的补码是10000000。(规定的)这里有一个表格可以让你更好的理解它。
在这里插入图片描述

由于要把它以整型的方式打印,因此要整型提升。这里说明一下,char类型整型提升成int,unsigned char才整型提升成unsigned int。

提升后:
11111111 11111111 11111111 10000000
以无符号整型拿出来,因此这段二进制代表的数字是
在这里插入图片描述
习题3:

#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}

答案:和上一题一样。

128是无法正常存入char类型中的,因为char只能表示-128~127的数字(看上面那个表)

这里有一个神奇的规律。整型在越界的情况下,它会循环回到起点。
假如-128是char类型的起点,127是char类型的终点。127往后跨多一步(加一)会变成-128.

图解,看着这个表就很好理解了:
在这里插入图片描述
习题4:
打印什么?

int main()
{
	int i= -20;
	unsigned  int  j = 10;
	printf("%d\n", i+j); 
}

答案:-10

  1. 这道题有一个很简单的理解方法(但计算机不是这么做的)
    10是正数,所以unsigned对它没有意义,相当于j也是int类型。于是答案是-10.
    2.计算机的做法是常规做法。int和unsigned int相加,由于类型不统一,需要进行算数转换

注:整型提升和算数转换的区别,整型提升针对的char和short类型的。算数转换是针对int以上精度的类型的(下面的转换成上面的类型)。

  1. long double
  2. double
  3. float
  4. unsigned long int
  5. long int
  6. unsigned int
  7. int

图解:
在这里插入图片描述
习题5:
打印什么?

unsigned int i;
for(i = 9; i >= 0; i--)
{
    printf("%u\n",i);
}

答案:死循环。且打印9,8,7,…0,4294967295,4294967294,…
这个和习题三是一样道理的。由于unsigned int每一个数字都大于等于0,因此循环永远也不会结束。且会一直绕着那个起点与终点循环。如果不理解的话照着上面的图,自己尝试画一下。

习题6:
长度是多少呢?

int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0;
}

答案:255.
这道题和习题三也是异曲同工。不懂可以看那个图。
strlen是遇到\0就停下来,不再计数了。
细心就会发现数组是存放着
-1,-2,-3…-128,127,126,125…2,1,0,-1…
0前面刚好255个数字所以答案是255.

习题7:
会打印什么?

unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}

答案:死循环。
unsigned char可以表示的数字是0~255。到255之后再加一会继续变成0。(和习题三同理),所以会死循环

#总结

  1. 整型越界后会回到原来的起点。不同类型的起始点和终点都不同,自己可以写出对应的补码来计算。
  2. 一切操作都是对补码进行操作,不要拿着原码当补码!!!!!

2.浮点数的存储方式

#标准描述

浮点数的存储方式是人为规定的。(IEEE 754标准),有点复杂。

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:

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

S------Sign,指符号位
M------Mantissa,指尾数,这里的尾数指的是有效数字小数点后的尾数。M的取值范围是1~2,这个后面举例讲
E------Exponent,指幂

(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

具体方法是:

  1. 将一个浮点数写成二进制
  2. 将这个二进制写成科学计数法
  3. 把对应的S,M,E存入内存当中

举例来说: 十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。 那么,按照上面V的格式,可以得出s=0,
M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。

注:

  1. 小数点后面的二进制是2的负数次方。如:0.5的二进制是0.1,因为2的负一次方等于0.5**
  2. 一个十进制小数要能用浮点数精确地表示,最后一位必须是 5(当然这是必要条件,并非充分条件)。规律推演如下面的示例所示:
    在这里插入图片描述

#内存视角

那S,M,E具体是怎么存进内存当中的呢?


float类型(32位)
在这里插入图片描述
一个位存符号位,8个位存指数位,23个位存有效数字位(尾数位)


double类型(64位)
在这里插入图片描述
一个位存符号位,11个位存指数位,52个位存有效数字位(尾数位)

2.1M的存放方式

由于一个二进制数字写成科学计数法的时候,小数点前一位肯定是1。因此IEEE754规定默认第一位是1,存储有效数字位的时候就不存1了,只存放后面的尾数。

2.2E的存放方式

E由于可以为负数,因此不能直接存。标准规定要加上一个中间数来存放。float类型加上127,double类型加上1023.

若E=2,则放进内存的时候是放进2+127=129.

2.3 浮点数从内存中取出的方式(3种情况)

下面的E指的是存入内存的E(即加上127或1023后的)

  1. E不全为0或不全为1
    这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前
    加上第一位的1。
  2. E全为0
    E全为0的情况就是原先幂为-127。(-127+127=0)
    这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为
    0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
  3. E全为1
    E全为1的情况是原先幂为128。(128+127=255,即全一)
    这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
    (M不是全0也是一个很大的数)

用以上规则举个例子
在这里插入图片描述

2.4 应用题

打印什么?

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

答案:9,0.000000,1,091,567,616,9.000000

要讲的是第二和第三个。


第二个:
由于n是一个整型。

内存中存储的是0 00000000 00000000000000000001001

V=(-1)^0 ×0.00000000000000000001001×2(-126)=1.001×2(-146)
显然,V是一个很小的接近于0的正数,所以用十进制小
数表示就是0.000000。


第三个:

9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130

那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。 所以,写成二进制形式,应该是s+E+M,即

0 10000010 001 0000 0000 0000 0000 0000

换成十进制即:
在这里插入图片描述

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值