计算机中的整数
有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,第一位符号位都是用0表示“正”,用1表示“负”,其余位为数值位。
正数
的原码、反码、补码都相同
负整数
的三种表示方法各不相同。
原码
直接将负整数按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
对于整形
(有符号、无符号)来说:数据存放内存中其实存放的是补码
原因:
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器
)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路,也就是说只需要一套硬件电路就可以解决问题何乐不为。
加法减法到底是如何统一运算呢,这里举个例子:
20-10 就可以转化为 20+(-10) 两个数的补码进行相加
总结:
所有的整数
在内存中存储都是以补码
的形式存储,有符号的整数,与无符号数的原码、反码、补码相同,而负整数的原码、反码、补码要经过计算,为什么整数在内存中要以补码的形式存储,根本原因就是将减法也统一成加法(1-1 == 1+(-1) ),计算机中只有运算加法的硬件。
要想彻底理解整形在内存中的存储,就一定一定要理解
在表达式计算
时,表达式中的字符和短整型
操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
一句话就是运算过程中,数据要传输到CPU中的寄存器进行运算,而32位的CPU的寄存器为32位,所以像char这种8位不满32的数据,要先整形提升为32位数据,再送入CPU中运算,而整形提升是计算机帮我们做的所以叫隐式类型转化。
整形提升的规则:
整形提升是按照变量的数据类型的符号位来提升的
整形提升肯定是补码,因为存放在内存中的就是补码。
示例:
char a=127;
char b=2;
char c=0;
c=a+b;
printf("%d\n",c);
a和b的值被提升为普通整型,然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于c中。
下图,描述了整个过程一定一定要理解
我们都知道一个有符号的char 它的取值范围为 [-128 ~127 ] ,其实你不管给char类型的变量赋值一个多大的整数,但在存储在该变量的值一定是在 [-128 ~127 ]范围内,其根本原因就是char 类型只有8位,将一个整形的数据放入一定会产生截断。
例题:
其实这里的出现这种情况的根本原因是char 类型的变量,或者短整形的变量,把存进来的最高位1,当成了符号位所以是一个负数。
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
下面思考一个问题:
或者直接是这样:
既然你说一个int类型与unsigned int 类型 的变量进行运算时,肯定b的类型也要转化为unsigned int 类型,那么问题来了,两个无符号数相加为什么能加出负数来呢。
先抛出问题,等讲完变量内容的存入和取出再来讲上面那个问题。
unsigned int a= -10 ,-10是如何存储在变量a中,如何取出
字面数据必须先转成补码
,在放入空间当中。所以所谓符号位,完全看数据本身是否携带±号。和变量有符号还是无符号无关
!
一定要先看存储数据的变量本身类型
,然后才决定要不要看最高符号位。如果变量是有符号的类型(比如 signed int),则如果符号位为1 则需要将补码转化成原码,若符号为0 直接二进制转成十进制 (正数的原码就等于补码),如果不需要(比如unsigned int),直接二进制转成十进制(无符号数原码也等于补码)。
计算机存储方式分为两种:大端存储 与 小端存储
大端存储: 是指数据的低位(从右往左由低到高) 存储在内存的高地址中,而数据的高位存储到内存的低地址中。
小端存储:是指数据的低位(从右往左由低到高) 存储在内存的低地址中,而数据的高位存储到内存的高地址中。
为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节
。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址,也就是说一个字节的内存单元有一个地址。
在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:
定义一个整型变量 int a=0x44332211;
看看变量a在内存中如果存储
检测一下我的电脑是什么存储模式
接下来就要判断计算机存储方式是小端还是大端
定义一个整型变量a
int a=1;
int Check_Sys1()
{
int a=1;
return *(char *)&a;
}
int main()
{
int ret=Check_Sys2();
if (1==ret)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
unsigned char 的取值范围:
在做题之前,有关整形提升,还有整数的原码、反码、补码的概念搞的清清楚楚,不然做题会有问题
整形提升的规则:
整形提升是按照变量的数据类型的符号位来提升的
整形提升肯定是补码,因为存放在内存中的就是补码。
无符号
整形提升,高位直接补0
在计算时,内存中是以补码的形式进行运算,记住补码发吗出来就是方便运算,所以这个整数的真正的值是它原码的大小,所以打印一定是打印整数的原码。
习题:
习题二:
答案:
具体分析:
按整形(有符号与无符号)的形式打印时,若数据不够整形需要整形提升
习题三:
答案:
具体分析:
因为是以十进制的有符号数打印,整形提升之后,判断符号位是否为负数,若为负数要将补码转化为原码再化成十进制进行打印,若是整数直接转化为十进制打印(正数因为补码就是原码)
解决这个题目的重点就是,不管你往char 类型的变量中放什么整数,都在[ -128~127 ]范围之内。
具体分析:
无符号数,从内存中拿出来运算
,一定为正数,不管里面存储的是什么二进制序列(就算是负数的二进制补码)。
虽然两个数据的值一样,但变量的类型不一样则导致在内存中存储的形式就有巨大差异。
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754对有效数字M和指数E,还有一些特别规定。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去
。这样做的目的,是多一位有效数字。
E为一个无符号整数(unsigned int)
这意味着,如果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。
浮点数5.5在内存中如何存储:
浮点数0.5(E为负数)在内存中如何存储:
指数E从内存中取出:
E全为0
说明原来我们E=0-127 =-127,数据的数量级为 1*2^-127 直接表示为趋近与0的数据。
E全为1
说明原来我们E=255-127=128,数据的数量级为 1*2^128 直接表示为无穷大的数据。
由此可知float类型的取值范围应该在:
E为全1 ,在加上前面的正负号,就是取值范围