什么是“2's Complement”?

我学的是计算机系统课程,部分内容是苦苦挣扎的。我想了解它,但我读过的所有东西都没有把它放在一起。我读过维基百科的文章和其他各种文章,包括我的课本

因此,我想开始这篇社区维基文章来定义Two's Complement是什么,如何使用它,以及它如何在强制转换(从有符号到无符号,反之亦然)、逐位操作和位移位操作等操作中影响数字。

我所希望的是一个程序员很容易理解的一个清晰简洁的定义

465819 次浏览

二进制补码是一种存储整数的聪明方法,因此常见的数学问题非常容易实现。

为了理解,你必须考虑二进制中的数字。

它基本上是说,

  • 对于0,用所有的0。
  • 对于正整数,开始向上计数,最大值为2(比特数- 1)-1。
  • 对于负整数,做完全相同的事情,但是转换0和1的角色并开始倒数(所以从0000开始,从1111开始-这是“补”。部分)。

让我们尝试使用一个4位的迷你字节(我们将其称为一点一点地咬 - 1/2字节)。

  • 0000 - 0
  • 0001 - 1
  • 0010 - 2
  • 0011 - 3
  • 01000111 - 4到7

这是我们目前能找到的阳性结果。2__abc0-1 = 7。

负面影响:

  • 1111 - 1
  • 1110 - 2
  • 1101 - 3
  • 11001000 - - 4到- 8

请注意,负数会得到一个额外的值(1000 = -8),而正数则没有。这是因为0000用于零。这可以被认为是计算机的数轴

区分正数和负数

这样做,第一个比特得到了“签名”的作用;位,因为它可用于区分非负十进制值和负十进制值。如果最高有效位是1,则二进制值可以说是负数,如果最高有效位(最左边)是0,则可以说十进制值是非负数。

“Sign-magnitude"负数只是将其正数对应数的符号位翻转,但这种方法必须处理将1000(一个1后面跟着所有__abc2)解释为“负零”;这很令人困惑。

“的”complement"负数只是正数的位补,这也导致了令人困惑的“负零”。1111(所有的1)。

除非你的工作非常接近硬件,否则你可能不需要处理个位补或符号幅度整数表示。

我想知道是否有比维基百科上的文章更好的解释。

你试图用2的补表示法解决的基本问题是存储负整数的问题。

首先,考虑一个存储在4位的无符号整数。您可以拥有以下内容

0000 = 0
0001 = 1
0010 = 2
...
1111 = 15

这些是无符号的,因为没有指示它们是负的还是正的。

符号大小和多余符号

要存储负数,您可以尝试一些方法。首先,您可以使用符号幅度表示法,它将第一个位指定为符号位来表示+/-,其余位表示幅度。还是用4位假设1代表- 0代表+那么你就有

0000 = +0
0001 = +1
0010 = +2
...
1000 = -0
1001 = -1
1111 = -7

所以,你看到问题了吗?我们有正0和负0。更大的问题是二进制数的加减法。使用符号幅度进行加减法的电路将非常复杂。

是什么

0010
1001 +
----

?

另一个系统是多余的符号。你可以存储负数,你可以摆脱两个0的问题但加减法仍然很困难。

于是就有了二的补。现在您可以存储正整数和负整数,并相对轻松地执行算术。有许多方法可以将一个数转换为二的补数。这是一个。

将十进制转换为二的补数

  1. 将数字转换为二进制(暂时忽略符号) 例如,5是0101,-5是0101

  2. 如果这个数字是正数,那么你就完成了。 例如,5是二进制的0101,使用二补表示法

  3. 如果数字是负的,那么

    3.1找到补体(逆0和1) 例如,-5是0101,所以找到补体是1010

    在补体1010 + 1 = 1011上加1。 因此,-5在2的补码中是1011
那么,如果你想在二进制中做2 +(-3)呢?2 +(-3) = -1。 如果你用符号的大小来加这些数,你需要做什么?0010 + 1101 = ?

使用2的补码,想想会有多简单。

 2  =  0010
-3 =  1101 +
-------------
-1 =  1111

将2的补数转换为十进制

将1111转换为十进制:

  1. 这个数从1开始,所以它是负的,所以我们找到1111的补,也就是0000。

  2. 将1加到0000,得到0001。

  3. 将0001转换为十进制,即1。

  4. 应用符号= -1。

大作。

我喜欢lavinio的回答,但变换部分增加了一些复杂性。通常情况下,可以选择在保留符号位的情况下移动位,或者不保留符号位。这是将数字处理为有符号数字(-8到7表示小块,-128到127表示字节)或全范围无符号数字(0到15表示小块,0到255表示字节)之间的选择。

想象一下,你有有限数量的比特/比特/数字等等。将0定义为所有数字都为0,并自然向上计数:

00
01
02
..

最终你会溢出。

98
99
00

我们有两位数字,可以表示从0到100的所有数字。所有这些数字都是正数!假设我们也想表示负数?

我们真正拥有的是一个循环。2之前的数字是1。1之前的数字是0。0之前的数字是…99

为了简单起见,我们设任何大于50的数都是负数。0 ~ 49代表0 ~ 49。“99”是-1,“98”是-2,…“50”是-50。

这个表示是十的补。计算机通常使用二进制补码,它是相同的,只是使用位而不是数字。

10的补数的好处在于加法只是工作。你不需要做任何特殊的加法和负数!

通过对给定数的第1个补数加1,可以得到2个补数。 假设我们必须找出10101的两个补,然后找到它的一个补,即010101加到这个结果中,即01010+1=01011,这是最终答案

这是一种对负整数进行编码的聪明方法,该方法将数据类型中大约一半的位组合保留给负整数,并且将大多数负整数与其对应的正整数相加会导致进位溢出,使结果为二进制零。

因此,在2的补码中,如果1是0x0001,那么-1是0x1111,因为这将导致0x0000的组合和(溢出1)。

像我看到的大多数解释一样,上面的解释清楚如何使用2的补码,但并没有真正解释它们在数学上。我会试着这么做,至少对整数来说是这样的,我会先介绍一些你们可能熟悉的背景知识。

回想一下十进制是如何工作的:
  2345
是一种书写
  2 ×103. + 3. ×102 + 4 ×101 + 5 ×10 __abc8。

以同样的方式,二进制是一种只使用01来写数字的方法,遵循相同的思路,但将上面的10替换为2。然后在二进制中,
  1111
是一种书写
  1 ×23. + 1 ×22 + 1 ×21 + 1 ×210
,如果你算出来,结果等于15(以10为底)。这是因为它是
  8+4+2+1 = 15。

这对于正数来说很好。它甚至适用于负数,如果你愿意在负数前面加一个负号,就像人类对待小数一样。在某种程度上,这甚至可以在计算机上完成,但我从20世纪70年代初就没见过这样的计算机了。我将把原因留到另一个讨论。

对于计算机来说,使用补充表示负数更有效。这里有一些经常被忽视的东西。补表示法涉及到数字数字的某种反转,甚至是在正常正数之前隐含的零。这很尴尬,因为问题来了:所有这些?这可能是一个无限的数字要考虑。

幸运的是,计算机并不代表无穷。数字被限制在特定的长度(或者宽度,如果你喜欢)。所以让我们回到正二进制数,但有一个特定的大小。在这些例子中,我将使用8个数字(“位”)。所以我们的二进制数实际上是
  00001111

  0 ×27 + 0 ×26 + 0 ×25 + 0 ×24 + 1 ×200 + 1 ×201 + 1 ×203 + 1 ×2 __abc15

为了形成2的补负,我们首先将所有(二进制)数字补全,形成
  11110000
,然后加上1,形成
  11110001
,但我们如何理解这意味着-15?

答案是,我们改变了高阶位(最左边的位)的含义。对于所有负数,该位将是1。这个变化将是改变它在数字中所占值的符号。所以现在我们的11110001被理解为代表
  -1 ×27 + 1 ×26 + 1 ×25 + 1 ×24 + 111100010 ×2111100011 + 0 ×2111100012 + 111100010 ×2111100014 + 1 ×2111100016
注意到表达式前面的“-”了吗?这意味着符号位的权重为-27,即-128(以10为基数)。所有其他位置保留无符号二进制数中相同的权值。

算出-15,结果是-128 + 64 + 32 + 16 + 1用计算器试试。它是-15。

在我所见过的计算机中表示负数的三种主要方式中,2的补码因为方便而在一般使用中胜出。不过,它有一个奇怪的地方。因为它是二进制的,所以必须有偶数个可能的位组合。每个正数都可以和负数配对,但只有一个零。负零等于零。所以还有一个组合,符号位是1的数字,其他地方都是0。对应的正数不符合所使用的比特数。

关于这个数字更奇怪的是,如果你试图通过互补和加1来形成正数,你会得到相同的负数。0会这样做似乎很自然,但这是出乎意料的,完全不是我们习惯的行为,因为除了计算机,我们通常认为数字是无限供应的,而不是这种固定长度的算术。

这只是怪胎的冰山一角。表面之下还有更多的东西在等待着,但这就足够我们讨论了。如果你研究定点算术中的“溢出”,你可能会发现更多。如果你真的想深入了解它,你可能还会研究“模算术”。

2的补码对于查找二进制值非常有用,但是我想到了一个更简洁的方法来解决这样的问题(从未见过其他人发布它):

以二进制为例:1101(假设空格“1”是符号)等于3

使用2的补码,我们可以这样做…翻1101到0010…加上0001 + 0010 ===>得到0011。0011的正二进制= 3。因此1101 = 3!

我意识到: < / >强

而不是所有的翻转和加法,你可以只做一个基本的方法来解决正二进制(假设0101)是(23. * 0) + (22 * 1) + (21 * 0) + (20 * 1) = 5。

用否定句做同样的概念!(稍微扭曲一下)

以1101为例:

对于第一个数字,do -(23. * 1) = 8,而不是23. * 1 = 8

然后像往常一样,执行8 + (22 * 1) + (21 * 0) + (20 * 1) = 3

让我们用8位二进制形式得到答案10 - 12: 我们真正要做的是10 + (-12)

我们需要用12的恭维部分减去10。 12的二进制值是00001100。 10的二进制值是00001010,

为了得到12的恭维部分,我们只需将所有位反转,然后加1。 12的二进制反转是11110011。这也是逆码(一个人的补码)。 现在我们需要添加一个,现在是11110100.

所以11110100是12的赞美!这样想很简单。

现在你可以用二进制形式来解决上面的10 - 12问题了。

00001010
11110100
-----------------
11111110

2的补语:当我们用一个数字的1的补语加一个额外的1时,我们将得到2的补语。例如:100101它的1的补码是011010和2的补码是011010+1 = 011011(通过添加1的补码)更多信息

从数学的角度来看这两个补体系统是有道理的。在ten的补语中,这个想法本质上是“隔离”差异。

示例:63 - 24 = x

我们把24的补数相加,也就是(100 - 24)实际上,我们要做的就是在方程两边加100。

现在方程是:100 + 63 - 24 = x + 100,这就是为什么我们要去掉100(或10或1000或其他)。

由于必须从一长串零中减去一个数字的不方便情况,我们使用“减基数补”系统,在十进制系统中,9的补。

当我们看到一串大的9减去一个数时,我们只需要把数字倒过来。

例如:99999 - 03275 = 96724

这就是为什么在9的补数之后加1。你可能从儿时的数学中知道,9通过“偷走”1变成了10。所以基本上就是10的补位差减去1。

在二进制中,2的补数等于10的补数,而1的补数等于9的补数。主要的区别在于,我们不是试图用10的幂来分离差异(将10、100等添加到等式中),而是试图用2的幂来分离差异。

正是因为这个原因,我们把比特位颠倒。就像小数中的被减数是一串9一样,二进制中的被减数也是一串1。

例如:111111 - 101001 = 010110

因为1链比2的幂小1,它们从差值中“偷”了1,就像小数点中的9一样。

当我们使用负二进制数时,我们实际上是在说

0000 - 0101 = x

1111 - 0101 = 1010

1111 + 0000 - 0101 = x + 1111

为了“分离”x,我们需要加1,因为1111离10000只有1,我们去掉前导的1,因为我们只是把它加到原始的差值上。

1111 + 1 + 0000 - 0101 = x + 1111 + 1

10000 + 0000 - 0101 = x + 10000

只要两边都去掉10000就得到x,这是基本的代数。

最简单的答案:

1111 + 1 =(1)0000。所以1111一定是-1。那么-1 + 1 = 0。

理解这些对我来说是完美的。

你也可以使用在线计算器来计算一个十进制数的补二表示:http://www.convertforfree.com/twos-complement-calculator/

到目前为止,许多答案都很好地解释了为什么2的补数被用来表示负数,但没有告诉我们2的补数是什么,尤其是没有告诉我们为什么加了一个“1”,而且实际上经常以错误的方式加。

这种混淆来自于对补数定义的不理解。补语是指使某物完整的缺失部分。

根据定义,以b为基数的n位数x的基数补是b^n-x。 在二进制中,4由100表示,它有3位数字(n=3)和基数2 (b=2)。所以它的基数补是b^n-x = 2^3-4=8-4=4(或二进制的100)。< / p >

然而,在二进制中,求一个基数的补并不像求它的消简基数补那么容易,消简基数补定义为(b^n-1)-y,只比基数补小1。要得到一个减少的基数补,只需翻转所有的数字。

100 -> 011(减基数补位)

为了得到基数(2的)补,我们只需按定义加1。

011 +1 ->100(2的补码)。

现在有了这个新的理解,让我们来看看给出的例子 Vincent Ramdhanie(见上面的第二个回应)

/*开始文森特

将1111转换为十进制:

这个数从1开始,所以它是负的,所以我们找到1111的补,也就是0000。 0000加上1,得到0001。 将0001转换为十进制,即1。 应用符号= -1。 大作。< / p >

结束文森特*/

应该理解为

数字从1开始,所以是负的。所以我们知道它是x的一个2的补。为了找到由它的2的补表示的x,我们首先需要找到它的1的补。

2的x的补码:1111 x的补数:1111-1 ->1110; X = 0001,(翻转所有数字)

应用符号-,结果=-x =-1。

参考:https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html

我把所有位的倒数加1。编程:

  // in C++11
int _powers[] = {
1,
2,
4,
8,
16,
32,
64,
128
};


int value=3;
int n_bits=4;
int twos_complement = (value ^ ( _powers[n_bits]-1)) + 1;

Two的补语主要用于以下原因:

  1. 避免0的多个表示形式
  2. 避免在溢出的情况下跟踪进位(如补位)。
  3. 进行简单的加法和减法运算变得很容易。

我读了一个奇妙的解释在Reddit由jng,使用里程表作为类比。

enter image description here

这是一个有用的约定。同样的电路和逻辑操作 在二进制中加减正数仍然对正数有效 如果按照惯例是负数,这就是为什么它是这样的

想象一下一辆汽车的里程表,它的转速是99999。如果你 增加00000,得到00001。如果减去00000,就得到99999 (由于翻滚)。如果你把1加回99999,它就回到 00000. 所以确定99999代表-1是很有用的。同样,确定99998表示-2是非常有用的,依此类推。你有 停一下,按照惯例,是数字的上半部分 被认为是负的(50000-99999),而下半部分是正的 只代表自己(00000-49999)。结果,上面的数字 5-9表示表示的数字为负,为0-4 表示所表示的是正的-与顶部的位完全相同

表示二进制补数中的符号 理解这一点对我来说也很难。有一次我拿到了,又回到 重新阅读书籍、文章和解释(当时没有互联网 当时),事实证明,许多描述它的人并不是真的 理解它。之后我确实写了一本教授汇编语言的书 那个(确实卖了10年).

2对给定数字的补码是no。通过1和no的1的补数相加得到。 假设,我们有一个二进制不。: 10111001101 它的1的补码是:01000110010 它的2的补码是:01000110011

按位补一个数就是将其中的所有位翻转。对2的补位,我们翻转所有的位,加1。

对有符号整数使用2的补码表示,我们应用2的补码操作将正数转换为负数,反之亦然。因此,以nibbles为例,0001(1)变成1111(-1),并再次应用该op,返回0001

零处操作的行为有利于给出零的单一表示,而无需特别处理正零和负零。0000补充1111,当添加1时。溢出到0000,给我们一个0,而不是一个正数和一个负数。

这种表示的一个关键优点是,用于无符号整数的标准加法电路在应用于它们时产生正确的结果。例如,在nibbles中添加1和-1:0001 + 1111,位溢出寄存器,留下0000

作为一个温和的介绍,优秀的Computerphile制作了关于这个主题的视频

补一词源于完备性。在十进制世界中,数字0到9提供了一个补充(完整集)的数字或数字符号来表示所有的十进制数。在二进制世界中,数字0和1提供了数字的补充来表示所有二进制数。事实上,符号0和1必须用来表示所有东西(文本、图像等)以及正(0)和负(1)。 在我们的世界中,number左边的空白被认为是零:

                  35=035=000000035.

在计算机存储位置没有空白空间。所有位(二进制数字)必须为0或1。为了有效地使用内存,可以将数字存储为8位、16位、32位、64位、128位表示形式。当存储为8位数字的数字被传输到16位位置时,符号和幅度(绝对值)必须保持不变。1的补语和2的补语表示都有助于实现这一点。 作为名词: 1的补位和2的补位都是有符号量的二进制表示,其中最有效的位(左边的位)是符号位。0表示正,1表示负。 2s补不等于负。它表示一个有符号的量。在十进制中,大小表示为正数。该结构使用符号扩展来保存数量,当提升到一个寄存器[],有更多的比特:

       [0101]=[00101]=[00000000000101]=5 (base 10)
[1011]=[11011]=[11111111111011]=-5(base 10)

作为动词: 2的补码是否定。这并不意味着消极。意思是如果负数变成正数;如果是正的就是负的。幅度是绝对值:

        if a >= 0 then |a| = a
if a < 0 then |a| = -a = 2scomplement of a
这个能力允许高效的二进制减法,先求反后加。 A -b = A + (-b)

1的补数的官方方法是每一位数用1减去它的值。

        1'scomp(0101) = 1010.
这与逐个翻转或反转每一位是一样的。结果是- 0,这是不受欢迎的,所以给te 1的补码加上1就解决了这个问题。

.

.

.
        Example 1                             Example 2
0101  --original number              1101
1's comp  1010                       0010
add 1     0001                       0001
2's comp  1011  --negated number     0011

在这些例子中,否定也适用于符号扩展数。

< p >: < br > 1110进位111110进位 0110与000110相同 1111年 111111年 Sum 0101 Sum 000101

减法:

    1110  Carry                      00000   Carry
0110          is the same as     00110
-0111                            +11001
----------                        ----------
sum  0101                       sum   11111

请注意,当使用2的补码时,数字左侧的空白区域对于正数用0填充,而对于负数用1填充。进位总是被加上,必须是1或0。

干杯

2的补本质上是一种求二进制数的加性逆的方法。问自己这样一个问题:给定一个二进制形式的数字(存在于固定长度的内存位置),当将什么位模式添加到原始数字(在固定长度的内存位置)时,会使结果全为0 ?(在相同的固定长度存储器位置)。如果我们能得到这个位模式,那么这个位模式就是原始数字的-ve表示(加性逆);根据定义,将一个数与它的加性逆相加总是零。例如:取一个8位字节内的101为5。现在的任务是提出一个位模式,当添加到给定的位模式(00000101)会导致在存储5的内存位置全为0吗时,即字节的所有8位都应该为零。要做到这一点,从101的最右边的位开始,对于每个单独的位,再次问同样的问题:我应该向当前位添加哪位以使结果为零?考虑到通常的结转,继续这样做。在我们处理完最右3位(定义原始数字而不考虑前导零的数字)后,最后进位进入加性逆的位模式。此外,由于我们将原始数字保存在一个8位字节中,加法逆中的所有其他前导位也应该是1,因此(这很重要)当计算机添加“;(使用8位模式表示)和它的加法逆使用"that"存储类型(一个字节),则结果在那个字节中将为全零。

 1 1 1
----------
1 0 1
1 0 1 1 ---> additive inverse
---------
0 0 0

简单来说,2's Complement是一种在计算机内存中存储负数的方法。而正数则存储为普通二进制数。

让我们考虑这个例子,

计算机使用Binary Number System表示任何数字。

x = 5;

它表示为0101

x = -5;

当计算机遇到-符号时,它会计算它的2的补数并存储它。 i.e 5 = 0101,它的2的补码是1011.

计算机处理数字的重要规则是:

  1. 如果第一位是1,那么它必须是negative数字。
  2. 如果除第1位外的所有位都是0,则它是一个正数 因为在数字系统中没有-0。(1000 is not -0改为正8)
  3. 如果所有的位都是0,那么它就是0
  4. 否则它是positive number

2的补码是表示负数的一种方式,大多数控制器和处理器都以2的补码形式存储负数

问题是“什么是“2的补”?”对于那些想要从理论上理解它的人(以及我试图补充其他更实际的答案),简单的回答是:2的补码是对偶系统中负整数的表示,不需要额外的字符,如+和-。