上一篇博客咱们主要介绍了布尔代数和C语言当中的几个运算符。那么这一篇博客咱们主要介绍在计算机中整数是如何表示的,诸如咱们在编码过程当中遇到的对数据类型进行强制转换可能会获得意想不到的结果在这篇博客里你会获得解答。html
整数包含正整数,0,负整数。咱们从小的数学常识,整数是无穷无尽的,即整数的大小没有限制。java
可是在计算机中则不能这样理解,由于计算机是靠数字信号来表示数,计算机所能处理的整数的长度是由计算机的字长来决定的,因此,在计算机中,咱们必须制定一个规则来表示整数。程序员
C 语言是支持多种整型数据类型的,下面咱们看一下在 32 位机器和 64 位机器中,C 语言整型数据类型的取值范围。函数
咱们能够看到 :编码
①、C 语言数据类型是能够用来指定大小,同时还能够指示表示的数是非负数(声明为 unsigned),或者负数(默认)。spa
②、数据类型分配的字节数会根据机器的字长和编译器有所不一样,不一样的大小所表示的范围是不一样的。上图惟一一个与机器有关的取值范围是 long 类型的,64位机器使用8个字节(264),而32位机器使用4个字节(232)。3d
③、负数的范围要比正数的范围大1。这是为何呢,请接着往下面看。htm
下面咱们看一下 C 语言标准所定义的每种数据类型所能表示的最小的取值范围。blog
C 语言标准咱们能够从上图获得:get
①、正数和负数的取值范围是对称的。
②、int 数据类型能够用 2 个字节来实现。(216)
③、long 数据类型用4 个字节来实现。(232)
无符号数,在C语言中,即用 unsigned 声明的整数。
定义:假设对于一个w位的无符号整数,用二进制比特位能够表示为[xw-1 , xw-2 , … , x2 , x1 , x0]。那么咱们能够用一个函数表示以下:
这个函数能够举几个简单的例子来看:
那么很显然,对于一个无符号编码的数,由 w 位的二进制序列构成,那么它的最小值,即全部位都为 0 ,用位向量表示即:【000......000】。
UMinw = 0
最大值即全部位都为 1,用位向量表示即:【111......111】
UMaxw = 1 * (1-2w) / 1 - 2 = 2w - 1
咱们能够得出一个结论:无符号的二进制,对于任意一个w位的二进制序列,都存在惟一一个整数介于0 到 2w-1之间,与这个二进制序列对应。反过来,在0 到 2w-1之间的每个整数,存在惟一的二进制序列与其对应。
上面咱们讲解了正整数的编码,那么在实际应用中,是存在负数的。而在计算机中,最多见的表示有符号的数就是补码。补码的定义以下:
其中最高有效位 xw-1 也称为符号位,符号位为 1 时表示负数,当设置为 0 时,表示非负数。下面咱们看几个例子:
那么咱们能够得出:当最高位为1,其他为所有是 0 的时候,即【1000......000】,表示补码格式的最小值:
TMinw = -2w-1
当最高位为 0,其他为所有是 1 时,即【0111......111】,表示补码格式的最大值:
TMaxw = 1 * (1 - 2w-1) / 1 - 2 = 2w-1-1
经过上面的两个公式,咱们就很好理解为何上面C语言数据类型负数的范围要比正数的范围大1。
和上面无符号编码同样,咱们对于补码格式编码也能够获得一个结论:
对于任意一个w位的二进制序列,都存在惟一一个介于-2w-1 到 2w-1-1的整数,与这个二进制序列对应。反过来,对于任意介于-2w-1 到 2w-1-1的整数,存在惟一的长度为w二进制序列与其对应。
那么你就应该明白了为何十进制 -1,在计算机中二进制表示为 1111 1111,而不是1000 0001,由于计算机是以补码的形式表示的。
反码定义:除了最高有效位的权是-2w-1-1,而不是-2w-1其他的和补码表示方式同样
原码定义:最高有效位是符号位,用来肯定剩下的位是正仍是负
咱们能够和补码的定义进行对比:
原码:一个整数,按照绝对值大小转换为二进制数,最高位为符号位。
反码:将原码除最高位(符号位)外,其他各位按位取反,所获得的二进制码。正数的反码为原码。
补码:反码最低位加1即为补码。
对于正整数,原码、反码、补码彻底同样,即符号位固定为0,数值位相同。
对于负整数,原码和补码互相转换的简便方法:从数的右边往左开始数,遇到“0”不理它,直到遇到第一个“1”为止,之后的每一位数取反便是它的原码或补码,符号位不变,仍是“1”(补码的补码是原码)。
好比:11010100 ----- 从右往左数,第一位是0,不理它,第二位仍是0不理它,第三位是1,那么今后之后的每位取反,即为它的补码了.答案为:10101100
事实上,程序员若是但愿代码具备最大的可移植性,可以在全部可能的机器上运行,就应该用补码的形式来表示有符号整数。虽然过去生产过基于反码表示的机器,可是几乎全部的现代机器都是使用补码。
注意:浮点数有使用原码编码。
关于整型数据类型的表示和取值范围,Java标准是很是明确的,它要求采用补码形式,取值范围和C语言在64位机器中的状况同样。在Java中,单字节数据类型称为 byte,而不是char,并且没有long long 数据类型。这些具体的要求都是为了保证不管在什么机器上,Java程序运行的表现都能彻底同样。
在 信息的存储和表示 这篇博客中咱们讲过计算机在解释一个数据类型的值时主要有四个因素:位排列规则(大端或者小端)、起始位置、数据类型的字节数、数据类型的解释方式。对于特定的系统来讲,前两种因素都是特定的,而对于后两种因素的改变,则能够改变一个数据类型的值的最终计算结果,这就是强制类型转换。
那么考虑相同整数类型的无符号编码和补码编码,数据类型的大小是没有任何变化的,变化的就是它们的解释方式。好比1000这个二进制序列,若是用无符号编码解释的话就是表示8,而若采用补码编码解释的话,则是表示-8。
①、有符号数强转为无符号数
前面咱们说过:不管是无符号编码仍是补码编码,其映射方式都是双射,所以它们都必定存在逆映射。若是咱们定义U2Bw(x)为B2Uw(x)的逆映射,则对于任意一个整数x,若是0 =< x < 2w,通过U2Bw(x)的计算以后,将获得惟一一个二进制序列。一样的,若是咱们定义T2Bw(x)为B2Tw(x)的逆映射,则对于任意一个整数x,若是-2w-1 =< x < 2w-1,通过T2Bw(x)的计算以后,也将获得惟一一个二进制序列。
能够很明显的看出,对于0到2w-1-1这个区间内的整数来讲,两种编码获得的二进制序列是同样的。为了获得其它区间里的整数的映射关系,咱们定义:
T2Uw(x) = B2Uw(T2Bw(x))
这个函数表明的含义是补码编码转换为无符号编码的时候,先将补码编码转换为二进制序列,再将二进制序列转换为无符号编码,最终也就是补码编码转为无符号编码的计算。
下面咱们简单的推算一下上面的定义,到底是如何转换的,也就是有符号数 x 和与之对应的无符号数T2Uw(x) 的关系。咱们将上面无符号编码和补码编码的公式相减,
将0到w-2的位的加权和互相抵消),即 B2Uw(x) - B2Tw(x) = xw-12w-1 - (-xw-12w-1) = xw-12w
将等式左边的B2Tw(x)移到等式右边,即 B2Uw(x) = xw-12w + B2Tw(x)
此处咱们令x为T2Bw(x),则 B2Uw(T2Bw(x)) = xw-12w + B2Tw(T2Bw(x)) = xw-12w + x
即 T2Uw(x) = xw-12w + x
此时考虑xw-1的状况,当xw-1为1时,也就是补码编码表示负数的时候,T2Uw(x)则为2w + x 。(此时x为负数,也就是说2w + x < 2w)
若xw-1为0时,则补码编码为正数,此时T2Uw(x) = x 。
综上可知,有下列式子成立
从这个式子中能够很明显的看出,最终获得的无符号数范围为0 =< x < 2w。
下图为表示补码编码与无符号编码的对应关系,能够看出在0至2w-1-1之间,二者是相等的,而其他区间则不一样。
从上图咱们也能够得出:当将一个有符号数映射为它相应的无符号数时,负数就被转换成了大的正数;而非负数会保持不变。
这里咱们看一个小例子来理解一下:
#include <stdio.h> int main() { char t = 0xFF; unsigned char u = (unsigned char)t; //%d把对应的整数按有符号十进制输出,%u把对应的整数按无符号十进制输出 printf("t=%d,t2u=%u\n",t,u); return 0;//c标准规定建议main函数返回值为int }
输出结果为:
这个结果怎么解释呢,首先有符号char t=0xFFFF。这是由于C语言在64位系统中占用一个字节,转换成二进制数即:1111 1111,转换为补码也是:1111 1111,咱们套用下面补码的公式能够获得:
1111 1111的值为 -1。而后根据咱们上面的转换公式:
能够获得转换以后的值为 -1+28=255。也就是上面打印的结果。
②、无符号数转换为有符号数
相反,咱们用一样的方式也能够证实从无符号编码到补码编码的公式,咱们依然将无符号编码和补码编码的公式相减
即 B2Uw(u) - B2Tw(u) = uw-12w-1 - (-uw-12w-1) = uw-12w
即 B2Tw(u) = B2Uw(u) - uw-12w
此时咱们令u为U2Bw(u),则 B2Tw(U2Bw(u)) = B2Uw(U2Bw(u)) - uw-12w = u - uw-12w
即 U2Tw(u) = u - uw-12w
此时考虑uw-1的状况,当uw-1为0时,也就是无符号编码数值小于2w-1的时候,U2Tw(u)则为u 。
若uw-1为1时,也就是无符号编码数值大于或等于2w-1的时候,此时U2Tw(u)= u - 2w。(此时U2Tw(u)为负数,由于 u < 2w)
综上,咱们能够获得无符号编码转换为补码编码的公式
一样的,在0至2w-1-1之间,二者依然是相等的,而其他区间则不一样。
仍是看一下下面的例子来理解:
#include <stdio.h> int main() { unsigned char u = 0xFF; char t = (char)u; //%d把对应的整数按有符号十进制输出,%u把对应的整数按无符号十进制输出 printf("u=%u,u2t=%d\n",u,t); return 0;//c标准规定建议main函数返回值为int }
输出结果:
这应该很好理解了,无符号 0xFF,即1111 1111,采用的是无符号编码,第一位不是符号位,那么转换为十进制就是255,而后套用上面的公式:u-2w=255-28=-1
本篇博客主要讲解了有符号数和无符号数之间的转换,咱们须要明白它的原理,这篇博客也涉及到不少公式推导证实,LZ也是看了好几遍才理解这些,你们若是第一遍看不懂也不要紧,多看几遍,而后多用笔推导推导,仍是不难理解的。下一章会介绍C语言中的有符号数和无符号数以及扩展和截断数字。