计算机中,正数、负数是怎么区分的呢,如何存放正数和负数?这里,就要用到补码这个概念了,先给出结论吧:正数和负数在计算机其实都是使用补码来存放的,并且在计算机中是没有减法运算的,减法实际上就是补码直接相加

正数和负数的补码

补码是计算机存放数据之前对数据做了一种转换操作得到的,与补码相关的几个名词还有原码、反码:

1、原码:字节的最高位为符号位,其余表示数值大小,最简单;
2、反码:正数的反码和原码一样,负数的反码除最高位符号位外,其他位都取反;
3、补码:在反码的基础上加1,这样可以方便计算机进行计算,可以让**最高位符号位都能参与计算**;

正数的补码就是原码本身,负数的补码是其反码加1,我们以C语言为例:

# short占用两字节内存,最高位为符号位
short a = 8;
short b = -8;

原码

补码

a

0000 0000 0000 1000

0000 0000 0000 1000

b

1000 0000 0000 1000

1111 1111 1111 1000

得到了a、b的补码,我们来模拟一下计算机算一下8-8的值,其实就是直接把a b的补码相加:
0000 0000 0000 1000 + 1111 1111 1111 1000 = 1 0000 0000 0000 0000
由于short总共只有两字节,所以结果中的最高位 1 要舍弃,最后得到0000 0000 0000 0000,也就是0

整数反转

介绍完了正数和负数的存储方式,下面说一下整数反转的问题。以C语言的有符号数为例:

int a = 0x80000000;

int型总共占4字节,因此内存中的a变量应该是下面这样子:

1000 0000 0000 0000 0000 0000 0000 0000

这串二进制数字如果直接按照数学规则转成十进制的话,应该是 2147483648,但是根据之前的定义我们知道,有符号数的最高字节应该是符号位,所以对于计算机而言,这个二进制数是一个负数,所以上面这个二进制串其实是一个负数的补码形式,因此如果我们直接输出a的十进制会得到一个负数,这其实就是整数反转(int的取值范围是-2147483648 ~ 2147483647,2147483648超出了这个范围)。
我们可以手动把上面的二进制串当作补码,反向转换一下,补码 - 1再取反码即可得到原码。
首先计算负数反码,也就是补码-1,也就是补码加上-1的补码,:

1000 0000 0000 0000 0000 0000 0000 0000
+ 1111 1111 1111 1111 1111 1111 1111 1111
------------------------------------------
1 0111 1111 1111 1111 1111 1111 1111 1111

(ps:
这里我们发现符号位已经溢出了,这是因为补码计算中符号位是可以参加计算的,我们始终以结果的最高位作为符号位,不过在C语言环境中,如果直接用0x80000000 - 1,其实是会把溢出的符号位舍弃掉(因为我们要把结果存入一个int型的四字节变量里),也就是会得到:0111 1111 1111 1111 1111 1111 1111 1111,转成十进制就是2147483647)

然后我们把反码转成原码(最高位是符号位,符号位外的取反):

1 0111 1111 1111 1111 1111 1111 1111 1111
转为:
1 1000 0000 0000 0000 0000 0000 0000 0000

因此得到结果 -2147483648
也就是说,直接输出a的话会得到-2147483648:

printf("%d", a);
输出结果:
-2147483648

综上,我们可以知道整数反转为什么会发生
在C语言中,如果我们定义一个int a,然后赋值一个超过了2147483647的正数,那么a在存放这个数字的时候,符号位会发生变化——计算机只会保留二进制数字的最后32位,把前面的都舍弃掉,然后把截取后的二进制数的最高位视为符号位,因此导致实际存放的数字会发生正负数反转,比如:

int a = 2147483647 + 1;
printf("%d", a);
输出结果:
-2147483648

使用负数补码正确存放十进制大正数

了解了正、负数在计算机内存中的存放方式以及整数反转,那么如何在不改变数据类型的前提下正确存放一个十进制大正数到内存里呢?
这个场景其实会存在于进程间数据交互的情况,比如我用一个python脚本发送了一个大正数到C语言开发的一个接口,然后这个数字超过了int能表示的最大的正数,但实际上二进制长度并没有超过int的内存大小。
其实,其实如果不需要关心数据格式化输出的正负和数字多少,只关心二进制数据是否正确的话,我们只需要保证二进制数据一样就可以了,比如我们想把正数2147483649存到int型变量里:

2147483648的数学方法转成二进制为1000 0000 0000 0000 0000 0000 0000 0001

如果我们只要想办法在int变量的内存中放一样的二进制数据,就相当于实现了2147483649的存放,只不过格式化输出这个变量还是会有问题,但二进制数据是一致的~
我们知道1000 0000 0000 0000 0000 0000 0000 0001转成int型十进制,对应的数字为-2147483647,因此如果我们想存放2147483649到int里,应该使用负数补码来赋值,也就是说要把十进制大正数的数学意义的二进制数据看做是负数补码,然后转成相应的负数来赋值,比如2147483649的二进制如果当做负数补码,对应的负数为-2147483647,可得出转换公式伪代码:
int new = old - 232(old为大正数)