C学习:有符号数类型的负数在计算机中的存储

附: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 000000002^31 = 21474836482. 取反:     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

各自优缺点:

  • 原码最好理解,但是加减法不够方便,还有两个零
  • 反码稍微困难一些,解决了加减法的问题,但还是有个零
  • 补码理解困难,其他就没什么缺点了

参考链接


  1. C语言中负数的存储方式