我正在上计算机系统课程,并且在某种程度上一直在努力与Two's Complement交流 。 我想了解它,但是我读过的所有内容都没有为我带来帮助。 我已经阅读了维基百科文章和其他各种文章,包括我的教科书 。
因此,我想开始这个社区Wiki帖子,以定义Two's Complement是什么,如何使用它以及它如何在诸如强制类型转换(从有符号到无符号,反之亦然),按位操作和移位操作等操作期间影响数字。
我希望有一个清晰简洁的定义 ,使程序员容易理解。
#1楼
二进制补码是一种存储整数的聪明方法,因此常见的数学问题很容易实现。
要理解,您必须考虑二进制数。
它基本上说,
- 对于零,请使用全0。
- 对于正整数,以最大2 (位数-1) -1开始计数。
- 对于负整数,请执行完全相同的操作,但是请切换0和1的角色(因此,与其以0000开头,不以1111开头-这是“补码”部分)。
让我们尝试使用4位的小字节(我们将其称为半字节-1/2字节)。
-
0000
零 -
0001
一 -
0010
两个 -
0011
三 -
0100
至0111
四至七
这是我们可以肯定的。 2 3 -1 = 7。
对于底片:
-
1111
负数 -
1110
负二 -
1101
负三 -
1100
至1000
负四至负八
请注意,对于负数,您将获得一个额外的价值( 1000
= -8),对于正数,您将获得一个额外的价值。 这是因为0000
用于零。 这可以视为计算机的编号行 。
区分正数和负数
这样做,第一位具有“符号”位的作用,因为它可用于区分正和负十进制值。 如果最高有效位是1
,那么二进制可以说是负数,而最高有效位(最左边)好像是0
,则可以说辨别十进制值为正。
“一个人的恭维”负数只是翻转符号位,然后从0开始计数。但是这种方法必须将1000
解释为“负零”,这令人困惑。 通常,您只需要在靠近硬件的情况下担心这一点。
#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。更大的问题是加和减二进制数。 使用符号幅度进行加法和减法的电路将非常复杂。
什么是
0010
1001 +
----
?
另一个系统是多余的符号 。 您可以存储负数,摆脱了两个零的问题,但是加法和减法仍然很困难。
随之而来的是二进制补码。 现在,您可以存储正整数和负整数,并相对轻松地执行算术运算。 有很多方法可以将数字转换为二进制补码。 这是一个
将小数转换为二进制补码
将数字转换为二进制(现在忽略符号),例如5为0101,-5为0101
如果数字为正数,则说明您已经完成。 例如5是二进制补码表示法的0101。
如果数字为负,则
3.1找到补码(将0和1求反),例如-5是0101,所以找到补码是1010
3.2将1加到补码1010 +1 =1011。因此,二进制补码中的-5为1011。
那么,如果您想以二进制形式进行2 +(-3)怎么办? 2 +(-3)为-1。 如果您使用符号幅度将这些数字相加,该怎么办? 0010 + 1101 =?
使用二进制补码考虑一下它会多么容易。
2 = 0010
-3 = 1101 +
-------------
-1 = 1111
将Two的补码转换为十进制
将1111转换为十进制:
数字以1开头,因此为负数,因此我们找到了1111的补码,即0000。
将1加到0000,我们得到0001。
将0001转换为十进制数1。
应用符号= -1。
多田!
#3楼
我喜欢lavinio的回答,但移位位会增加一些复杂性。 通常,在尊重符号位或不尊重符号位的情况下,都可以选择移动位。 您可以在将数字视为带符号的数字(半字节为-8至7,字节为-128至127)或全范围无符号数字(半字节为0至15,字节为0至255)之间进行选择。
#4楼
想象一下,您拥有有限的位数/技巧/数字/其他。 您将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。
这个表示是十的补码 。 计算机通常使用二进制补码 ,除了使用位代替数字外,其余都是相同的。
十进制补码的优点是加法有效 。 您无需执行任何特殊操作即可添加正数和负数!
#5楼
这是一种巧妙的编码负整数的方法,数据类型的位组合的大约一半保留给负整数,大多数负整数与它们对应的正整数相加会导致进位溢出将结果保留为二进制零。
因此,在2的补码中,如果1为0x0001,则-1为0x1111,因为这将导致总和为0x0000(溢出为1)。
#6楼
就像我所见过的大多数解释一样,上面的解释很清楚如何使用2的补码,但是并没有真正从数学上解释它们。 我将尝试这样做,至少对于整数,我将介绍一些可能首先熟悉的背景。
回想一下十进制的工作方式:
2345
是一种写作方式
2 ×10 3 + 3 ×10 2 + 4 ×10 1 + 5 ×10 0 。
同样,二进制是遵循相同的一般思想而仅使用0和1写入数字的方式,但是用2s代替了上面的10s。 然后以二进制形式
1111
是一种写作方式
1 ×2 3 + 1 ×2 2 + 1 ×2 1 + 1 ×2 0
如果计算出来,结果等于15(以10为底)。 那是因为
8 + 4 + 2 + 1 = 15。
对于正数来说,这一切都很好。 如果您愿意在负号前面加上一个减号,那么它甚至适用于负数,就像人类对十进制数所做的那样。 这甚至可以在计算机上完成,但自1970年代初以来我还没有见过这样的计算机。 我将保留其他讨论的理由。
representation for negative numbers. 对于计算机,事实证明,对负数使用表示更为有效。 这是经常被忽略的东西。 补码表示法涉及数字的某种反转,甚至是正常正数之前的隐含零。 这很尴尬,因为出现了问题:所有人? 这可能是要考虑的无数个数字。
幸运的是,计算机不代表无限。 数字被限制为特定的长度(或宽度,如果您愿意)。 因此,让我们返回具有特定大小的正二进制数。 在这些示例中,我将使用8位数字(“位”)。 所以我们的二进制数将是
00001111
要么
0 ×2 7 + 0 ×2 6 + 0 ×2 5 + 0 ×2 4 + 1 ×2 3 +1×2 2 + 1 ×2 1 + 1 ×2 0
为了形成2的补数负数,我们首先对所有(二进制)数字求和以形成
11110000
并加1到表格
11110001
但是我们如何理解-15呢?
答案是我们更改了高位(最左边的一位)的含义。 对于所有负数,该位将为1 。 变化将是将其贡献的符号更改为它所显示的数字的值。因此,现在我们的11110001被理解为代表
-1 ×2 7 + 1 ×2 6 + 1 ×2 5 + 1 ×2 4 + 0 ×2 3 + 0×2 2 + 0 ×2 1 + 1 ×2 0
注意表达式前面的“-”吗? 这意味着符号位的权重为-2 7 ,即-128(以10为底)。 所有其他位置保持与未签名二进制数字相同的权重。
算出我们的-15,是
-128 + 64 + 32 + 16 +1
在计算器上尝试一下。 是-15。
在我看到的负数出现在计算机中的三种主要方式中,2的补码会赢得通用性,以方便使用。 不过,这很奇怪。 由于它是二进制的,因此必须有偶数个可能的位组合。 每个正数可以与它的负数配对,但是只有一个零。 否定零将使您零。 因此,还有另一种组合,即符号位带有1的数字,其他所有位置均为0 。 相应的正数将不适合所使用的位数。
关于这个数字,更奇怪的是,如果您尝试通过补码和加数来形成它的正数,则会得到相同的负数。 零看起来很自然,但这是意料之外的,根本不是我们习惯的行为,因为除了计算机之外,我们通常认为数字的供应不受限制,而不是这种固定长度的算法。
这就像是一堆奇怪的冰山一角。 在表面之下还有更多的等待,但这足以进行讨论。 如果研究定点算术的“溢出”,可能会发现更多信息。 如果您真的想研究它,您可能还会研究“模块化算术”。
#7楼
2的补码对于查找二进制值非常有用,但是我想到了一种更简洁的方法来解决此类问题(从未见过其他人发表过):
以二进制文件为例:1101 [假设空格“ 1”是符号”等于-3 。
使用2的补码,我们将执行此操作...将1101翻转到0010 ...加0001 + 0010 ===>给出我们0011.0011正二进制= 3.因此1101 = -3 !
我意识到:
而不是进行所有的翻转和加法运算,您只需做求解正二进制的基本方法即可(假设0101)为(2 3 * 0)+(2 2 * 1)+(2 1 * 0)+(2 0 * 1)= 5。
做一个完全相反的概念!
以1101为例:
对于第一个数字而不是2 3 * 1 = 8 ,请执行-(2 3 * 1)= -8 。
然后照常继续,执行-8 +(2 2 * 1)+(2 1 * 0)+(2 0 * 1)= -3
#8楼
让我们使用8位二进制形式获得10 – 12的答案:我们真正要做的是10 +(-12)
我们需要得到12的补余部分,以从10中减去它。二进制的12为00001100。二进制的10为00001010。
要获得12的补码部分,我们只需反转所有位,然后加1。二进制反转的12就是11110011。这也是反码(一个补码)。 现在我们需要添加一个,现在是11110100。
所以11110100是12的补充! 这样想起来很容易。
现在您可以以二进制形式解决以上10-12的问题。
00001010
11110100
-----------------
11111110
#9楼
2的补码:当我们在数字的1的补码上加上一个额外的数字时,我们将得到2的补码。 例如:100101,它的1的补码为011010,而2的补码为011010 + 1 = 011011(通过将1与1的补码相加)。 有关详细信息,本文以图形方式进行了说明。
#10楼
从数学的角度来看两者的补码系统确实很有意义。 用十的补码,想法是本质上“隔离”差异。
例如:63-24 = x
我们加上24的补数,实际上是(100-24)。 所以说真的,我们要做的是在方程式的两边加100。
现在的等式是:100 + 63-24 = x + 100,这就是为什么我们删除100(或10或1000或其他)的原因。
由于必须从零位长链中减去一个数字的不便,我们使用了“减基数补码”系统,在十进制中使用了9的补码。
当我们看到一个从九个大数字链中减去的数字时,我们只需要反转数字即可。
例如:99999-03275 = 96724
这就是为什么在9的补码之后加1。您可能从童年数学中就知道,“偷” 1会使9变成10。因此,基本上只是10的补码使差值减1。
在Binary中,二的补码等于十的补码,而一的补码与九的补码相等。 主要的区别是,我们没有尝试隔离乘方为10的差(将10、100等添加到方程式中),而是尝试隔离乘方为2的差。
出于这个原因,我们将位反转。 就像我们的minuend是十进制的9的链一样,我们的minuend是二进制的1的九的链。
例如:111111-101001 = 010110
因为1的链比1的2的幂小1,所以它们像9的十进制一样从差中“窃取” 1。
当我们使用负二进制数时,我们实际上只是在说:
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,这是基本的代数。
#11楼
最简单的答案:
1111 + 1 =(1)0000。 因此1111必须为-1。 然后-1 + 1 = 0。
完全了解这些对我来说是完美的。
#12楼
通过在给定数字的1'至1'补码上加两个补码。 可以说,我们必须找出10101
二进制补码,然后找到其补码,即01010
将此结果加1
,即01010+1=01011
,这是最终的答案。
#13楼
您还可以使用在线计算器来计算十进制数字的二进制补码二进制表示形式: http : //www.convertforfree.com/twos-complement-calculator/
#14楼
到目前为止,许多答案很好地解释了为什么使用二进制补码来表示负数,但没有告诉我们二进制补码是什么,尤其是为什么不加上“ 1”,而实际上却常常以错误的方式添加。
混乱来自对补数定义的理解不充分。 补码是可以使某些内容完整的缺失部分。
根据定义,基数b中n位数字x的基数补码为b ^ nx。 在二进制4中用100表示,它有3个数字(n = 3)和基数2(b = 2)。 因此其基数补码为b ^ nx = 2 ^ 3-4 = 8-4 = 4(或二进制为100)。
但是,在二进制运算中,获得基数补码并不像获得其减小的基数补码那样容易,后者的定义为(b ^ n-1)-y,仅比基数补数小1。 要减少基数补码,只需翻转所有数字即可。
100-> 011(小数补码)
要获得基数(二进制)补数,我们只需添加1作为定义的定义。
011 +1-> 100(二进制补码)。
现在有了这种新的理解,让我们看一下Vincent Ramdhanie给出的示例(请参见上面的第二个答复)
/ * Vincent的开始
将1111转换为十进制:
数字以1开头,因此为负数,因此我们找到1111的补数,即0000。将0000加1,得到0001。将0001转换为十进制,即1。应用符号= -1。 多田!
文森特结束* /
应该理解为
数字以1开头,因此为负数。 因此我们知道它是某个值x的二进制补码。 要找到以2的补码表示的x,我们首先需要找到其1的补码。
x的补码:1111; x的补码:1111-1-> 1110; x = 0001,(翻转所有数字)
应用符号-,答案= -x = -1。
#15楼
参考: 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;
#16楼
几周前我遇到了同样的问题。 我最终从各种来源在线阅读了有关它的内容,试图将它们拼凑在一起,然后亲自撰写有关它的内容,以确保我正确地理解了它。 我们使用二进制补码主要有两个原因:
- 避免0的多重表示
- 为了避免在溢出时跟踪进位(如补码)。
- 进行简单的操作(例如加法和减法)变得容易。
如果您想对问题进行更详细的说明,请尝试在此处撰写我的文章。 希望能帮助到你!
#17楼
我以里程表为类比,阅读了jng 关于Reddit的精彩解释。
这是一个有用的约定。 如果使用约定,则在二进制中加/减正数的相同电路和逻辑运算仍然可以在正数和负数上工作,这就是为什么它是如此有用且无所不在的原因。
想象一下汽车的里程表在(例如)99999处滚动。如果增加00000,则得到00001。如果减小00000,则得到99999(由于滚转)。 如果将99999加1,它将回到00000。因此确定99999表示-1非常有用。 同样,确定99998表示-2非常有用,依此类推。 您必须停在某个地方,并且按照惯例,数字的上半部分将被视为负数(50000-99999),而下半部分的正数将代表自己(00000-49999)。 结果,高位数字为5-9表示所代表的数字为负数,高位数字为0-4表示所代表的数字为正数-与表示二进制补码二进制数符号的最高位完全相同。
我也很难理解这一点。 一旦获得并返回以重新阅读书籍文章和解释(那时还没有互联网),结果发现很多描述它的人并不真正理解它。 在那之后,我确实写了一本教汇编语言的书(这本书卖了十年很不错)。
#18楼
给定数字的2的补码为no。 将1与1的补码相加得到。 假设我们有一个二进制数:10111001101它的1的补码是:01000110010它的2的补码是:01000110011
#19楼
按位补码是对数字中的所有位进行翻转。 为了补全它,我们翻转所有位并加一个。
对有符号整数使用2的补码表示形式,我们应用2的补码运算将正数转换为负数,反之亦然。 因此,以半字节为例, 0001
(1)变为1111
(-1)并再次应用op,返回0001
。
零操作的行为有利于给出零的单一表示,而无需对正零和负零进行特殊处理。 0000
为1111
补码,当添加1时。 溢出到0000
,给我们一个零,而不是一个正负1。
这种表示的主要优点是,无符号整数的标准加法电路在应用于它们时会产生正确的结果。 例如,在半字节: 0001 + 1111
加1和-1,这些位溢出寄存器,剩下0000
。
作为一个简短的介绍,出色的Computerphile制作了有关该主题的视频 。
#20楼
补语一词源自完整性。 在十进制世界中,数字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的补码上加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进位111110进位0110与000110相同1111 111111总和0101总和000101
减去:
1110 Carry 00000 Carry
0110 is the same as 00110
-0111 +11001
---------- ----------
sum 0101 sum 11111
请注意,在使用2的补数时,数字左侧的空白表示正数为零,而负数表示为1。 进位总是加进,必须为1或0。
干杯
#21楼
2的补码本质上是提出二进制数的加法逆的一种方法。 问问自己:给定二进制形式的数字,将什么位模式添加到原始数字后将使结果为零? 如果可以提出该位模式,则该位模式为原始数字的-ve表示(加反)。 因为按照定义,在其加和逆数上加一个数字应始终为零。 示例:以101(十进制5)为例。现在的任务是提出一个位模式,当将其添加到给定的位模式(101)时将得出零。 为此,请从101的最右边开始,并针对每个单独的位,再次询问相同的问题:我应在“此”位上添加哪一位以使结果为零? 考虑到通常的结转,继续这样做。 在完成最右边的3个位置(定义原始数字的数字而不考虑前导零)之后,最后一个进位进入加法逆的位模式。 此外,由于我们可以将原始数字保留为单个字节,因此加法逆的所有其他前导位也应为1,这样当计算机使用“ that”存储类型(char)将数字及其加法逆添加在一起时该char中的结果将全部为零。
1 1 1
----------
1 0 1
1 0 1 1 ---> additive inverse
---------
0 0 0
#22楼
简单来说, 2's Complement
是一种在计算机内存中存储负数的方法。 而正数存储为普通二进制数。
让我们考虑这个例子,
计算机使用Binary Number System
表示任何数字。
x = 5;
这表示为0101
。
x = -5;
当计算机产生符号-
,它将计算为2的补码并将其存储。 ie
5 = 0101,它的2的补码是1011
。
计算机用来处理数字的重要规则是,
- 如果第一位为
1
则它必须为negative
。 - 如果除第一位之外的所有其他位均为
0
,则为正数,因为数字系统中没有-0
。(1000 is not -0
而是正数8
) - 如果所有位均为
0
,则为0
。 - 否则它是一个
positive number
。