我在计算机系统课程中,并且一直在与Two's Complement挣扎。我想了解它,但我读过的所有内容并没有为我提供图片。我读过wikipedia article和其他各种文章,包括my text book。
因此,我想开始这个社区wiki帖子来定义Two's Complement是什么,如何使用它以及它如何在诸如强制转换(从有符号到无符号,反之亦然)等操作中影响数字,逐位操作和位移操作。
我所希望的是一个清晰简洁的定义,程序员很容易理解。
两个补码是一种存储整数的聪明方法,因此常见的数学问题很容易实现。
要理解,您必须考虑二进制数字。
它基本上说,
让我们尝试使用4位的迷你字节(我们称之为nibble - 1/2个字节)。
0000
- 零0001
- 一个0010
- 两个0011
- 三个0100
到0111
- 四到七这就是我们可以采取积极的态度。 23-1 = 7。
对于否定:
1111
- 负面的1110
- 负二1101
- 负三1100
到1000
- 负四到负八请注意,您为负数(1000
= -8)获得了一个额外值,而不是积极因素。这是因为0000
用于零。这可以被视为计算机的Number Line。
区分正数和负数
这样做,第一位获得“符号”位的作用,因为它可用于区分正十进制值和负十进制值。如果最重要的位是1
,那么二进制可以说是负数,其中最重要的位(最左边)是0
,你可以说识别十进制值是正数。
“一个人的恭维”负数只是翻转符号位,然后从0开始计算。但这种方法必须处理将1000
解释为“负零”这令人困惑。在靠近硬件工作时,通常只需要担心这一点。
我用jng读了一个奇妙的解释on Reddit,用里程表作为类比。
这是一个有用的惯例。如果使用约定,那么在二进制数中加/减正数的相同电路和逻辑运算仍可用于正数和负数,这就是为什么它如此有用和无所不在。
想象一下汽车的里程表,它在(例如)99999处滚动。如果你增加00000你得到00001.如果你减去00000,你得到99999(由于滚动)。如果你将一个加回到99999,它会回到00000.因此,确定99999代表-1是有用的。同样,确定99998代表-2非常有用,依此类推。你必须停在某个地方,而且按照惯例,数字的上半部分被认为是负数(50000-99999),而下半部分正数只能代表它们自己(00000-49999)。结果,最高位为5-9表示所表示的数字为负,而0-4表示所表示为正 - 与表示二进制补码二进制数的符号的最高位完全相同。
理解这一点对我来说也很难。一旦我得到它并重新阅读书籍文章和解释(当时没有互联网),结果发现很多描述它的人并没有真正理解它。之后我确实写了一本教授汇编语言的书(10年后卖得很好)。
它是一种巧妙的编码负整数的方法,使得数据类型的大约一半位组合被保留用于负整数,并且大多数负整数与其相应的正整数相加会导致进位溢出这使得结果为二进制零。
因此,如果一个是0x0001则为2的补码,则-1为0x1111,因为这将导致总和为0x0000(溢出为1)。
2的补充:当我们添加额外的1和1的补码时,我们将得到2的补码。例如:100101它的1的补码是011010而2的补码是011010 + 1 = 011011(通过添加1补充1)For more information本文以图形方式解释它。
我喜欢lavinio的答案,但是移位会增加一些复杂性。通常在尊重符号位或不遵守符号位时可以选择移动位。这是将数字视为带符号(半字节为-8到7,字节为-128到127)或全范围无符号数(半字节为0到15,字节为0到255)之间的选择。
几个星期前我遇到了同样的问题。我最后从各种来源在线阅读它,尝试将各个部分放在一起,并自己写一下,以确保我理解正确。我们使用两个补码主要有两个原因:
如果您想对手头的问题有更详细的解释,请尝试我写的文章here。希望能帮助到你!
补语这个词源于完整性。在十进制世界中,数字0到9提供数字或数字符号的补码(完整集)以表示所有十进制数。在二进制世界中,数字0和1提供数字的补码以表示所有二进制数。实际上,符号0和1必须用于表示所有内容(文本,图像等)以及正(0)和负(1)。在我们的世界中,数字左边的空白区域被视为零:
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.
这与单独翻转或反转每个位相同。这导致负零,这是不太受欢迎的,因此在te 1的补码中添加一个可以解决问题。要取消或取2s补码,先取1s补码然后加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
在示例中,否定也适用于符号扩展数字。
添加: 1110 Carry 111110 Carry 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的补码时,数字左侧的空格用正数填充零,但用负数填充。始终添加进位,必须为1或0。
干杯
参考: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;
给定数字的2的补码是no。通过添加1与1的补码得到1。假设,我们有一个二进制编号:10111001101它的1的补码是:01000110010它的2的补码将是:01000110011
对数字进行逐位补码是翻转其中的所有位。为了补充它,我们翻转所有位并添加一个。
使用2的补码表示有符号整数,我们应用2的补码运算将正数转换为负数,反之亦然。因此,使用半字节作为示例,0001
(1)变为1111
(-1)并再次应用op,返回0001
。
零操作的行为有利于在没有特殊处理正负零的情况下给出零的单个表示。 0000
补充了1111
,当加入1时。溢出到0000
,给我们一个零,而不是正面和负面。
这种表示的一个关键优点是无符号整数的标准加法电路在应用于它们时会产生正确的结果。例如,在半字节中添加1和-1:0001 + 1111
,这些位溢出寄存器,留下0000
。
对于一个温和的介绍,精彩的Computerphile已经产生了video on the subject。
您还可以使用在线计算器计算十进制数的二进制补码表示:http://www.convertforfree.com/twos-complement-calculator/
我想知道它是否可以比维基百科文章更好地解释。
您尝试使用二进制补码表示的基本问题是存储负整数的问题。
首先考虑以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.更大的问题是加和减二进制数。使用符号幅度进行加减的电路将非常复杂。
什么是
0010
1001 +
----
?
另一个系统是excess notation。你可以存储负数,你摆脱了两个零问题,但加法和减法仍然很困难。
所以随之而来的是两个补充。现在,您可以存储正负整数并相对轻松地执行算术运算。有许多方法可以将数字转换为二进制补码。这是一个。
那么,如果你想在二进制中做2 +( - 3)怎么办? 2 +( - 3)为-1。如果您使用符号幅度来添加这些数字,您需要做什么? 0010 + 1101 =?
使用两个补码考虑它是多么容易。
2 = 0010
-3 = 1101 +
-------------
-1 = 1111
将1111转换为十进制:
然后!
最简单的答案:
1111 + 1 =(1)0000。所以1111必须是-1。然后-1 + 1 = 0。
对我来说理解这些都是完美的。
像我看到的大多数解释一样,上面的解释清楚如何使用2的补码,但是没有真正解释它们在数学上是什么。我会尝试这样做,至少对整数而言,我会介绍一些可能先熟悉的背景。
回想一下十进制的工作原理: 2345 是一种写作方式 2×103 + 3×102 + 4×101 + 5×100。
以同样的方式,二进制是一种使用0和1编写数字的方法,遵循相同的一般想法,但用2替换上面的10。然后是二进制, 1111 是一种写作方式 1×23 + 1×22 + 1×21 + 1×20 如果你解决了,那就等于15(基数为10)。那是因为它是 8 + 4 + 2 + 1 = 15。
对于正数而言,这一切都很好。如果你愿意在他们面前贴一个减号,它甚至适用于负数,就像人类用十进制数字做的那样。这甚至可以在计算机中完成,但是从1970年代早期开始我就没有看过这样的计算机。我将留下不同讨论的理由。
对于计算机来说,使用补数表示来表示负数是更有效的。这是经常被忽视的东西。补语表示涉及数字数字的某种反转,甚至是正常正数之前的隐含零。这很尴尬,因为问题出现了:所有这些问题?这可能是要考虑的无限数字。
幸运的是,计算机并不代表无穷大。数字被约束到特定长度(或宽度,如果您愿意)。所以让我们回到正二进制数,但具有特定的大小。对于这些示例,我将使用8位数(“位”)。所以我们的二进制数确实如此 00001111 要么 0×27 + 0×26 + 0×25 + 0×24 + 1×23 + 1×22 + 1×21 + 1×20
为了形成2的补数否定,我们首先补充所有(二进制)数字以形成 11110000 并将1添加到表单中 11110001 但我们怎么理解这意味着-15?
答案是我们改变了高阶位(最左边的位)的含义。对于所有负数,该位将为1。改变将是改变它对其出现的数字值的贡献的符号。所以现在我们的11110001被理解为代表 -1×27 + 1×26 + 1×25 + 1×24 + 0×23 + 0×22 + 0×21 + 1×20 请注意那个表达式前面的“ - ”?这意味着符号位带有权重-27,即-128(基数为10)。所有其他位置保留与无符号二进制数相同的权重。
制定我们的-15,它是 -128 + 64 + 32 + 16 + 1 试试你的计算器吧。这是-15。
在我看到计算机中出现负数的三种主要方式中,为了方便一般使用,2的补码获胜。但它有一个奇怪的地方。由于它是二进制的,因此必须存在偶数个可能的位组合。每个正数可以与其负数配对,但只有一个零。否定零会让你变为零。所以还有一个组合,符号位为1,其他位置为0。相应的正数不符合正在使用的位数。
对于这个数字更奇怪的是,如果你试图通过补充和添加一个来形成积极的,你会得到相同的负数。看起来很自然零会这样做,但这是出乎意料的并且根本不是我们习惯的行为,因为除了计算机之外,我们通常会想到无限制的数字,而不是这种固定长度的算术。
这就像是一个奇怪的冰山一角。在表面之下还有更多的等待,但这对于这次讨论来说已经足够了。如果研究定点算术的“溢出”,你可能会找到更多。如果你真的想进入它,你也可以研究“模运算”。
2的补码对于找到二进制的值是非常有用的,但是我想到了解决这个问题的更简洁的方法(从未见过其他人发布它):
取二进制,例如:1101,[假设空格“1”是符号]等于-3。
使用2的补码我们会这样做...翻转1101到0010 ...添加0001 + 0010 ===>给我们0011. 0011 in positive binary = 3.因此1101 = -3!
我意识到的:
而不是所有的翻转和添加,你可以只做基本方法求解正二进制(假设0101)是(23 * 0)+(22 * 1)+(21 * 0)+(20 * 1) = 5。
用负面做完全相同的概念!(带小扭曲)
以1101为例,例如:
对于第一个数而不是23 * 1 = 8,请 - (23 * 1)= - 8。
然后像往常一样继续,做-8 +(22 * 1)+(21 * 0)+(20 * 1)= -3
想象一下,你有一个有限数量的位/ trits / digits /无论如何。您将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。
这种表示是十的补充。计算机通常使用二进制补码,除了使用位而不是数字之外,它们是相同的。
关于十个补充的好处是加法才有效。您不需要做任何特殊的事情来添加正数和负数!
通过添加给定数字的第1至第1个补码找出两个补码。让我们说我们必须找出10101
的两个补充然后找到它的补充,也就是说,01010
将1
添加到这个结果,即01010+1=01011
,这是最终的答案。
让我们用8位以二进制形式得到答案10 - 12:我们真正要做的是10 +( - 12)
我们需要得到12的赞美部分从10中减去它。二进制中的12是00001100.二进制中的10是00001010。
为了获得12的赞美部分,我们只是反转所有的位然后加1.12 in binary reverse是11110011.这也是反码(一个补码)。现在我们需要添加一个,现在是11110100。
所以11110100是12的补充!当你这么想的时候很容易。
现在你可以用二进制形式解决上面10-12的问题了。
00001010
11110100
-----------------
11111110
从数学的角度来看这两个补码系统真的很有意义。在十个补充中,想法是基本上“隔离”差异。
示例:63 - 24 = x
我们添加24的补充,实际上只是(100 - 24)。所以,我们所做的只是在等式的两边增加100。
现在等式是:100 + 63 - 24 = x + 100,这就是我们删除100(或10或1000或其他)的原因。
由于不得不从一长串零中减去一个数字的不方便,我们使用“缩小基数补充”系统,在十进制系统中,九个补码。
当我们从一个大的九连环中减去一个数字时,我们只需要反转这些数字。
示例:99999 - 03275 = 96724
这就是为什么,在九次补充之后,我们加1.你可能从童年时期的数学知道,9通过'偷'来变成10。所以基本上它只是10的补码,从差异中取1。
在Binary中,两个补码相当于十的补码,而一个补码与九的补码相等。主要区别在于,我们试图将差异与2的幂相隔离,而不是试图将差异与10的幂相加(将10,100等加入等式中)。
正是出于这个原因,我们将这些位反转。就像我们的minuend是十进制的九连串一样,我们的minuend是一个二进制的链。
示例:111111 - 101001 = 010110
因为1的链条低于2的良好幂,它们会从十进制中的九个等级中偷取“1”。
当我们使用负二进制数时,我们只是说:
0000 - 0101 = x
1111 - 0101 = 1010
1111 + 0000 - 0101 = x + 1111
为了“隔离”x,我们需要添加1,因为1111距离10000一个,我们删除了前导1,因为我们只是将它添加到原始差异中。
1111 + 1 + 0000 - 0101 = x + 1111 + 1
10000 + 0000 - 0101 = x + 10000
只需从两侧移除10000即可获得x,它是基本的代数。
到目前为止,许多答案很好地解释了为什么两个补码用来表示负数,但是不要告诉我们两个补码是多少,特别是不是为什么添加'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)。
然而,在二进制中,获得基数的补码并不像得到它的基数补集那么容易,其被定义为(b ^ n-1)-y,仅比基数补数小1。要获得缩小的基数补码,只需翻转所有数字即可。
100 - > 011(减少(一个)基数补语)
为了获得基数(二)的补集,我们简单地加1,作为定义的定义。
011 +1 - > 100(二进制补码)。
现在有了这个新的理解,让我们来看看Vincent Ramdhanie给出的例子(见上面第二个回复)
/ *文森特的开始
将1111转换为十进制:
数字以1开头,所以它是负数,所以我们找到1111的补码,即0000.加1到0000,我们得到0001.将0001转换为十进制,即1.应用符号= -1。田田!
文森特的结尾* /
应该理解为
这个数字从1开始,所以它是负数。所以我们知道它是某个值x的二进制补码。为了找到由它的两个补码表示的x,我们首先需要找到它的1的补码。
x的两个补码:1111 x的补码:1111-1 - > 1110; x = 0001,(翻转所有数字)
应用符号 - ,答案= -x = -1。