导读
大家好,很高兴又和大家见面啦!!!
在上一篇内容中我们介绍了无符号整数的表示与运算的内容:
- 无符号整数以二进制的形式存储在内存中;
- 在n位机器上,无符号整数的取值范围: 0 ~ 2 n − 1 0~2^n-1 0~2n−1
- 当数据长度超过存储字长时,存储时只会存储前n位,超出的部分将被舍弃;
- 为了节约开发成本,无符号整数的运算都是通过加法电路实现
- 无符号整数的加法规则,从低到高,逐位相加,逢二进一
- 无符号整数的减法实现:减数按位取反再加1,完成转化后与被减数相加
在今天的内容中,我们将会认识另一种整数形式——有符号整数。那什么是有符号整数呢?
有符号整数就是指的正整数、负整数和0,也就是数学里的整数集
Z
Z
Z 。在C语言中,通常用关键字来 signed
和 unsigned
来表示有符号数与无符号数。
而对于char
、short
、int
、long
、float
、double
……这些数据类型而言,他们所创建的变量均属于有符号数,这里我们还是以整型类型为例,如下所示:
这里我们并未直接给创建的变量赋值有符号整数的最大值与最小值,我们是通过给这些变量赋值无符号整型的最大值以此来验证他们的有无符号性。
在无符号整数中,最大值就是所有二进制位都是1,因此它对应的值应该是 2 n − 1 2^n-1 2n−1 ;而在有符号整数中,它的最大值目前我们只知道的是 2 n − 1 − 1 2^{n-1}-1 2n−1−1 ,而超出最大值的部分将从最小值开始记录,从输入的结果我们可以看到,当所有二进制位全为1时,这些变量对应的值都是 − 1 -1 −1 ,由此可以判定,这些变量均属于有符号整数。
有符号整数在内存中同样是以二进制的形式进行存储,那么它与无符号整数的区别在哪里呢?我们应该如何来区分无符号整数与有符号整数呢?下面我们就来围绕着这些问题进行探讨;
一、有符号整数的存储结构
从前面的学习中,我们对无符号整数与有符号整数是有一个初步了解的:
-
在无符号整数中,其二进制形式的每一个二进制位都表示的是一个数值,因此有符号整数对应的取值范围在: 0 ~ 2 n − 1 0~2^n-1 0~2n−1
-
而在有符号整数中,其二进制形式的最高位表示的是该数值的正负号,因此我们将这一位称为符号位:
- 符号位位0,数据为正数
- 符号位为1,数据为负数
-
除符号位外的其他二进制位则与无符号整数的二进制位一样,都是存储的数值信息,因此我们将这些二进制位称为数值位:
- 长度为n的有符号整数,其最高位即位权为 2 n − 1 2^{n-1} 2n−1 的二进制位为符号位,位权从 2 0 ~ 2 n − 2 2^0~2^{n-2} 20~2n−2 的二进制位为数值位
- 有符号整数的取值范围在: − 2 n − 1 ~ 2 n − 1 − 1 -2^{n-1}~2^{n-1}-1 −2n−1~2n−1−1
下面我们以长度为8位的数据来看一下,在存储结构上,有符号整数与无符号整数的区别,如下所示:
这里有朋友可能会奇怪,你不是说最高位是符号位吗?为什么有符号整数的最小值是
−
2
7
-2^7
−27 ?
要解答这个问题,那我们就必须知道有符号整数在内存中究竟是如何进行存储的。
二、有符号整数的表现形式
在有符号整数中,数据在存储时并不是像无符号整数那样,以数值对应的二进制形式进行存储,其根本原因就是正数与负数在运算上面是有区别的。
因此在有符号整数中,为了实现正数与负数之间的运算,数据在进行存储时会以其特定的形式存储在内存中。那具体是以哪种形式呢?
在有符号整数中,数值的二进制编码表示法主要有4种:
- 原码:用机器数的最高位表示数的符号,其余各位表示数的绝对值
- 补码:根据数值的正负,所对应着不同的补码,是有符号整数存储在计算机中的主要表现形式
- 反码:反码在计算机中很少使用,通常是作为数码变换的中间表示形式
- 移码:移码常用来表示浮点数的姐吗。它只能表示整数。
在接下来的内容中,我们将会一一介绍这4种编码的内容。今天我们将会了解到第一种编码形式——原码。
三、原码
在有符号整数中,它和无符号整数一样,存在着数值直接转化为二进制的形式,只不过与无符号整数不同的是,在有符号整数中,二进制的最高位为符号位,其中1代表负数,0代表正数。
这种用机器数的最高位表示数的符号,其余各位表示数的绝对值的形式称为有符号整数的原码。
3.1 原码与真值之间的转换
当我们用原码来表示真值时我们会将符号位单独隔离出来,如下所示:
- [ x ] 原 = 0 , x 1 x 2 x 3 x 4 … … [x]_原=0,x_1x_2x_3x_4…… [x]原=0,x1x2x3x4……
- [ x ] 原 = 1 , x 1 x 2 x 3 x 4 … … [x]_原=1,x_1x_2x_3x_4…… [x]原=1,x1x2x3x4……
其中
x
x
x 表示的是真值;
0
/
1
0/1
0/1 是符号位,0表示该值为正数,1表示该值为负数;
x
1
、
x
2
、
x
3
、
x
4
…
…
x_1、x_2、x_3、x_4……
x1、x2、x3、x4……表示的是二进制位
比如对于 ±49 这两个真值而言,在机器字长为8位的机器中,他们所对应的原码表示形式分别为:
- [ + 49 ] 原 = 0 , 00110001 [+49]_原=0,00110001 [+49]原=0,00110001
- [ − 49 ] 原 = 1 , 00110001 [-49]_原=1,00110001 [−49]原=1,00110001
从这里我们可以看到, ±49 他们只有符号位不同,数值位则是完全相同的。这里需要注意的是,如果我们没有限定机器字长,则数值位的高位0可以省略,如 ±49 可以表示为:
- [ + 49 ] 原 = 0 , 110001 [+49]_原=0,110001 [+49]原=0,110001
- [ − 49 ] 原 = 1 , 110001 [-49]_原=1,110001 [−49]原=1,110001
既然这样,我们不难推测在符号位为0与符号位为1的情况下,分别对应着各自的最大值与最小值:
-
符号位为0:
- 最大值: [ m a x ] 原 = 0 , 1111 … … [max]_原=0,1111…… [max]原=0,1111……
- 最小值: [ m i n ] 原 = 0 , 0000 … … [min]_原=0,0000…… [min]原=0,0000……
-
符号位为0:
- 最大值: [ m a x ] 原 = 1 , 0000 … … [max]_原=1,0000…… [max]原=1,0000……
- 最小值: [ m i n ] 原 = 1 , 1111 … … [min]_原=1,1111…… [min]原=1,1111……
因此在机器字长为n的机器中,有符号整数的原码所表示的数值的范围在 − ( 2 n − 1 − 1 ) ~ 2 n − 1 − 1 -(2^{n-1}-1)~2^{n-1}-1 −(2n−1−1)~2n−1−1 。
我们可以看到,在原码表示形式中,真值0的表示有正负两种形式,以机器字长8位为例,真值0的表示形式分别为:
- [ + 0 ] 原 = 0 , 0000000 [+0]_原=0,0000000 [+0]原=0,0000000
- [ − 0 ] 原 = 1 , 0000000 [-0]_原=1,0000000 [−0]原=1,0000000
3.2 原码的运算
既然原码的数值位与无符号整数的二进制形式一致,那是不是代表其运算的过程也是一致的能,下面我们就分别来看一下对于±49这两个值之间的加减运算,如下所示:
//原码的运算
void test2() {
//+49 + (-49) = 0 -> [0] = 0, 0000/[-0] = 1, 0000
[+49] = 0, 110001;
+ [-49] = 1, 110001;
------------------------
[-98] = 1, 1100010;
//A - B => A +(~B+1)
[-49] = 1, 110001 = > [~(-49) + 1] = 0, 001111;
//+49 - (-49) = 98 -> [98] = 0,1100010
[+49] = 0, 110001;
+[~(-49) + 1] = 0, 001111;
---------------------------
[+64] = 0, 1000000;
}
根据无符号整数的运算规则来对有符号整数的原码进行运算时,其运算的结果与真值的直接运算结果相差甚远,那么也就是说这种运算方式并不是有符号整型原码的运算方式。
从理论上来讲,原码异号间的减法: A − B A - B A−B 与同号间的加法: A + B A+B A+B 都是根据A的符号来进行确立的,而数值之间的运算则是可以根据同号之间的加法来进行,即±49的减法应该是:
//原码的运算——异号相减与同号相加
void test3() {
//+49 - (-49) => +49 + +49 = +98 -> [98] = 1, 1100010
[+49] = 0, 110001;
+ [+49] = 0, 110001;
------------------------
[+98] = 1, 1100010;
}
可以看到,此时的运算结果就没什么问题了,而对于同号之间的减法 A − B A-B A−B 与异号之间的加法 A + B A+B A+B 而言,其运算的结果则需要对符号进行判断,大致的思路为:根据 A A A 与 B B B 数值绝对值的大小来确定结果的符号,其结果的数值则是以绝对值大的数减去绝对值小的数进行获取,如下所示:
//原码的运算——同号相减与异号相加
void test3() {
//+49 + (-49) => |49| == |49| => 符号位为+
//|49| - |49| = 0 -> [0] = 0,000000
[+49] = 0, 110001;
- [-49] = 1, 110001;
------------------------
[+0] = 0, 000000;
}
理论上来说,整个运算过程应该如上所示,但是实际上是如何处理的这个我们就不再去深究。不过可以肯定的一点是,从经济的角度出发,计算机对于有符号整数的运算肯定是不会采用原码的运算方式的,原码的运算方式其性价比太低了,所以计算机内部的运算肯定是更加经济,更加简便的运算方式。
3.3 原码的优缺点
从上面的介绍中,我们不难看出,原码表示形式的优点在于与真值的对应关系简单、直观,与真值的转换简单,当我们用原码实现异号相减或同号相加时,其运算过程是十分简单的,拓展到乘除法也是如此。
但是在原码的表示中还是会存在一些不可忽视的问题:
- 真值0对应的原码表示形式有 [ + 0 ] 原 [+0]_原 [+0]原 和 [ − 0 ] 原 [-0]_原 [−0]原 两种;
- 原码的异号相加与同号相减的运算过程十分复杂
因此为了找到一种更加方便,更加简便的运算方法,便有了补码表示法。那什么是补码表示法呢?在下一篇内容中,我们将会详细介绍,大家记得关注哦!!!
结语
今天的内容到这里就全部结束了,在下一篇内容中我们将介绍《补码与反码》的相关内容,大家记得关注哦!如果大家喜欢博主的内容,可以点赞、收藏加评论支持一下博主,当然也可以将博主的内容转发给你身边需要的朋友。最后感谢各位朋友的支持,咱们下一篇再见!!!