C学习:有符号数类型的负数在计算机中的存储
引子
我们都知道正数在计算机中是转化成其二进制存储的,那么负数呢?
计算机中,负数统一采用的补码形式存储。所以变量被赋值负数后,本质存的就是补码,不用再手动转换成补码。
计算机中,我们最快看到负数补码
的方式,可参考以下代码:
int num = -10;
unsigned int numVer = num;
printf("0x%x\n", numVer);
最快得到负数补码
的方式,输出字符串bigOrder里按大端序输出对应补码,接上面代码:
int modVal = 0;
char bigOrder[9] = "00000000"; // 默认初始为0
char *p1 = &bigOrder[7]; // 从末尾开始放
while (num > 0) {
// 若num为0,则转换完成
modVal = num % 16; // 按正数转换成HEX表达
if (modVal >= 10) {
*p1 = (modVal - 10) + 'A';
} else {
*p1 = modVal + '0';
}
p1--;
num /= 16;
}
printf("%s\n", bigOrder);
定义
什么是补码呢?
正数的补码就是该正数本身,负数的补码需要转化,人工转化规则如下:
- 对负数取绝对值,用二进制表示
- 对每一位取反
- 对该数加1
简单来说,就是负数的补码为其绝对值取反后加1
所得的结果。
例子
-1 , -2 作为有符号数其存储在内存的二进制到底为多少?
负数以补码形式储存,而非其本身二进制本身,即符号位+绝对值
。比如,char
类型的-1
在计算机存储的不是1000 0001
;而是1111 1111
,即0xff
。
以char类型举例,详细说明下其转换过程。
char
类型默认为signed char
, 其取值范围是 -128 ~ 127
,即-2e7 ~ 2e7-1
, 用最高位表示其符号,0
表示正数,1
表示负数。
举例:-1 取绝对值0000 0001 -> 取反1111 1110 ->加1得到 1111 1111 0xff
举例:-2 取绝对值0000 0010 -> 取反1111 1101 ->加1得到 1111 1110 0xfe
注意:char类型的0 没有+0 和 -0 的区分,按上述操作,不管正负结果都是 0000 0000
用以下代码即可验证:
signed char x0 = 0xff; // -1
signed char x1 = 0xfe; // -2
signed char x3 = 0;
signed char x4 = -1;
printf("%d\n", x0);
printf("%d\n", x1);
printf("0x%x\n", x3);
printf("0x%x\n", x4);
以int类型为例,分析有符号数据类型的最小范围数值表示。
int
为4字节32位,其中首位用0表示正数,用1表示为负数,数值范围[-2^31, 2^31-1]
。
最大正数为:0x7fff ffff
(7的二进制为0111,f二进制为1111)
最大负数(-1)实际存储的补码为:0xffff ffff
最小负数(-2147483648)实际存储的补码为:0x8000 0000
(8的二进制为1000)
负数为源码取反码再取补码,以-1为例,过程如下:
1. 写原码: 10000000 00000000 00000000 00000001
2. 得反码: 11111111 11111111 11111111 11111110
3. 得补码: 11111111 11111111 11111111 11111111
重要重要,下面这个知识点最常出问题!!!
最小负数没有原码和反码
表示,最高位为1,其余位为0,就是最小负数。如-2147483648:
1. 原码:NA
2. 反码:NA
3. 补码:10000000 00000000 00000000 00000000
也可以用另一种方式求补码,可以不用考虑以上问题。过程是:先得绝对值,对其取反,再加1可得补码:
1. 绝对值: 10000000 00000000 00000000 00000000 (2^31 = 2147483648)
2. 取反: 01111111 11111111 11111111 11111111
3. 加1得补码:10000000 00000000 00000000 00000000
只有最小范围这个情况较特殊,结果变换后还是自己。
那为什么对应类型的最小负数无原码和反码表示呢?
- 首先本质是因为没有原码,所以没有反码。
- 没有原码的原因是,
-2147483648
的绝对值占用了符号位,导致必须往前加一位符号位才能表示,而此时已占用33位bit。 - 故无法用32位的int表示
-2147483648
的原码。 - 其实数据类型对应的最小负整数,本质上是跟最小正整数0对应的。
1 00000...
和0 00000...
是两种类型的0,第一个是符号位为1的0,第二个是符号位为0的0,刚好拿来表达最小负整数和最小正整数。
重要小结
- 正数的补码正常转换成二进制即可,首位符号位为0
- 负数的补码,最小值的二进制值最小,依次递增到-1,-1的二进制值最大
- 下面以int类型为例,做进一步说明:
INT_MIN
的补码为1000 0000 0000 0000 0000 0000 0000 0000
,即0x8000 0000
INT_MIN + 1
的补码为1000 0000 0000 0000 0000 0000 0000 0001
,即0x8000 0001
-1
的补码为1111 1111 1111 1111 1111 1111 1111 1111
,即0xffff ffff
- 所以,负数补码的二进制值是从
INT_MIN -> 100000...
,这样递增映射
扩展总结
原码、反码、补码:
- 正数的反码和补码都与原码相同
- 负数的反码为对该数的原码除符号位外各位取反
- 负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1
各自优缺点:
- 原码最好理解,但是加减法不够方便,还有两个零
- 反码稍微困难一些,解决了加减法的问题,但还是有个零
- 补码理解困难,其他就没什么缺点了
参考链接
C语言学习总结分享