C语言 03 原码 反码 补码
原码
计算机中所有的数字都是使用 0
和 1
这样的二进制数来进行表示的。
这时如果要存储一个数据,比如十进制的 3
,那么就需要使用 2 个二进制位来保存,二进制格式为 11
,占用两个位置,称为 2 bit 位。
一般占用 8 个 bit 位表示一个字节(B),2 个字节等于 1 个字,所以一个字表示 16 个 bit 位。
数字的直接二进制表示称为原码。
虽然原码表示简单,但是原码在做加减法的时候,就会出现问题。
以 8 bit 位为例:
1 + (-1)
取原码:
0000 0001
+
1000 0001
=
0000 1010
取十进制:
-2
显然结果应该为 0
,-2
的结果是错误的。
为了解决这一问题,引入了反码。
反码
正数的反码是其本身。
负数的反码是其原码符号位不变,其余各位取反。
经过上面的定义,再来进行加减法:
1 + (-1)
原码:
0000 0001
+
1000 0001
反码:
0000 0001
+
1111 1110
=
1111 1111
逆反码(取反):
1000 0000
十进制:
-0
这样虽然结果看起来对了。
但如果 1111 1111
代表 -0
,0000 0000
代表 +0
。
+0
和 -0
都等于 0
。
一个 0
用两个二进制数表示,既浪费也不合理,所以又引入了补码。
补码
正数的补码就是其本身
负数的补码是其原码符号位不变,其余各位取反(得到反码),最后 + 1
再来看上面的运算:
1 + (-1)
原码:
0000 0001
+
1000 0001
反码:
0000 0001
+
1111 1110
补码:
0000 0001
+
1111 1111
=
1 0000 0000
省略高位:
0000 0000
逆补码(本身):
0000 0000
逆反码(本身):
0000 0000
十进制:
0
这样就得到了预期的 0
。
可以看出,补码是计算机数字存储和运算的完美解决方案,所以计算机中的数字都是以补码存储。
C 语言使用的也是补码。
取值范围
按照上面原码的定义,可以根据位数推出取值范围。
比如现在一共有 8 bit 位来保存数据,为了表示正负,可以让第一个 bit 位专门来保存符号,这样能够表示的数据范围就是:
- 最小:
1111 1111 => -127
- 最大:
0111 1111 => 127
这里就有个疑问了,我们熟知的 char
占一个字节,也就是 8 bit 位,但它的取值范围是 -128
~ 127
。
-128
是从何而来呢?
计算机中的数字都是以补码存储,则:
-128
二进制:
1 1000 0000
逆补码(-1):
1 0111 1111
逆反码(取反):
1 1000 0000
十进制:
-128
可以看出,补码 -128
的原码也是 -128
由于是用 8 bit 位存储数据,1 1000 0000
所以应该舍弃最高位的 1
由此得到 1000 0000
按照惯性思维,1000 0000
应该为 -0
但前面说了,有了 0
不需要 -0
所以就用 1000 0000
表示 -128
了
这样运算也是不影响结果的:
1 + (-128)
原码:
0000 0001
+
1000 0000
=
1000 0001
逆补码(-1):
1000 0000
逆反码(取反):
1111 1111
十进制:
-127
再来看看代码的运行结果:
#include <stdio.h>
int main() {
char c = -128;
printf("%d", c);
}
-128
#include <stdio.h>
int main() {
char c = -128;
printf("%d", c + 1);
}
-127
由此可以看出计算机正是这样运算的。