我只是好奇,为了在二进制中表示-1,是否有一个原因,使用了2的补码:翻转位并加1?

-1由11111111(2的补码)表示,而不是(对我来说更直观)10000001,10000001是二进制1,第一位为负标志。

免责声明:我的工作不依赖二进制算术!


这样加法就不需要任何特殊的逻辑来处理负数了。在维基百科上查看这篇文章。

假设有两个数,2和-1。在表示数字的“直观”方式中,它们将分别为0010和1001(我坚持使用4位的大小)。两者互为补足,分别是0010和1111。现在,假设我想把它们相加。

2的补语加法非常简单。你通常加数字,任何进位在最后被丢弃。所以它们相加如下:

  0010
+ 1111
=10001
= 0001 (discard the carry)

0001是1,这是“2+(-1)”的预期结果。

但在你的“直观”方法中,添加更复杂:

  0010
+ 1001
= 1011

等于-3,对吧?简单的加法在这种情况下行不通。你需要注意,其中一个数字是负的,如果是这种情况,就使用不同的算法。

对于这种“直观的”存储方法,减法是一种不同于加法的操作,在加法之前需要对数字进行额外的检查。由于您希望最基本的操作(加法、减法等)尽可能快,因此需要以允许您使用尽可能简单的算法的方式存储数字。

此外,在“直观”存储方法中,有两个0:

0000  "zero"
1000  "negative zero"

它们直观上是相同的数字,但存储时有两个不同的值。每个应用程序都需要采取额外的步骤来确保非零值也不是负零。

以这种方式存储int型还有另一个好处,那就是当你需要扩展存储值的寄存器的宽度时。对于2的补数,在8位寄存器中存储一个4位数就是重复它的最高位:

    0001 (one, in four bits)
00000001 (one, in eight bits)
    1110 (negative two, in four bits)
11111110 (negative two, in eight bits)

这只是观察小单词的符号位,然后重复它,直到它赶上大单词的宽度。

使用你的方法,你需要清除现有的位,这是一个额外的操作,除了填充:

    0001 (one, in four bits)
00000001 (one, in eight bits)
    1010 (negative two, in four bits)
10000010 (negative two, in eight bits)

在这两种情况下,您仍然需要设置额外的4位,但在“直观”情况下,您还需要清除第5位。这是每个应用程序中最基本和最常见操作之一中的一个小小的额外步骤。


2的补数允许以正常的方式进行加减法(就像对无符号数字进行绕线运算一样)。它还防止了-0(一种单独的表示0的方法,如果使用常规的逐位比较数字的方法,它将不等于0)。


这是为了简化数字的和和和差。2的补数中一个负数和一个正数的和与正常方式的和是一样的。


维基百科说明了一切:

二补系统的优点是不需要加减电路检查操作数的符号来决定是加还是减。这一特性使系统实现更简单,能够轻松地处理更高精度的算术。此外,零只有一种表示,避免了与负零相关的微妙之处,这种微妙之处存在于补体系统中。

换句话说,无论数字是否为负,加法都是一样的。


使用2的补码是因为它更容易在电路中实现,也不允许负零。

如果有x位,2的补码范围从+(2^x/2+1)到-(2^x/2)。补码将从+(2^x/2)到-(2^x/2),但允许负数为零(0000在4位1的补码系统中等于1000)。


2的补语允许负数和正数相加,而不需要任何特殊的逻辑。

如果你想用你的方法做1和-1相加 10000001 (1) + 00000001 (1) 你得到 10000010 (2)

相反,通过使用2的补数,我们可以相加

11111111 (1) + 00000001 (1) 你得到 00000000 (0)

减法也是如此。

同样,如果你试着用6减去4(两个正数),你可以用2补4,然后把两者相加6 + (-4)= 6 -4 = 2

这意味着正数和负数的减法和加法都可以由cpu中的同一个电路完成。


扩展一下其他的答案:

在two's complement中

加法与普通正整数加法的原理相同。 减法也不会变 乘法!

"组织"需要不同的机制。

所有这些都是正确的,因为2的补是普通的模算术,我们选择通过减去模来看待一些负数。


该操作的通常实现是“翻转位并加1”,但有另一种定义它的方式可能使其原理更清楚。2的补数是通常的无符号表示形式,其中每一位控制2的下一次方,并使最有效项为负。

取8位值a7 a6 a5 a4 a3 a2 a1 a0

通常的无符号二进制解释是: 27*a7 + 26*a6 + 25*a5 + 24*a4 + 23*a3 + 22*a2 + 21*a1 + 20*a0 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255

两人的补充解释是: -27*a7 + 26*a6 + 25*a5 + 24*a4 + 23*a3 + 22*a2 + 21*a1 + 20*a0 = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1

其他任何位都不会改变含义,并且进位到a7是“溢出”,不期望工作,所以几乎所有的算术操作都可以工作而不需要修改(正如其他人所注意到的)。符号大小通常检查符号位,并使用不同的逻辑。


你的目的并不是把二进制数的所有位都颠倒过来。实际上就是用1减去每一位。这只是一个幸运的巧合,1减1得0,1减0得1。所以翻转位有效地执行了这个减法。

但为什么每个数字的差值都是1呢?你不是。您的实际目的是计算给定的二进制数与另一个具有相同位数但只包含1的二进制数的差值。例如,如果您的数字是10110001,当您翻转所有这些位时,您实际上是在计算(11111111 - 10110001)。

这解释了计算二的补的第一步。现在让我们在图中加入第二步——添加1。

将上述二进制方程加1:

11111111-10110001 + 1

你得到了什么?这样的:

100000000-10110001

这是最终方程。通过执行这两个步骤,你试图找到这个,最终的区别:二进制数减去另一个二进制数,多出一位,并且除最高位外都包含零。

但我们为什么要追求这种差异呢?好吧,从现在开始,我想你最好去读维基百科的文章。


我们对加减法都只做加法运算。我们将第二个操作数与第一个操作数相加。对于减法,我们将第二个操作数的2的补数与第一个操作数相加。

对于2的补码表示,我们不需要单独的数字组件,只使用加法器和补法器。


It's worthwhile to note that on some early adding machines, before the days of digital computers, subtraction would be performed by having the operator enter values using a different colored set of legends on each key (so each key would enter nine minus the number to be subtracted), and press a special button would would assume a carry into a calculation. Thus, on a six-digit machine, to subtract 1234 from a value, the operator would hit keys that would normally indicate "998,765" and hit a button to add that value plus one to the calculation in progress. Two's complement arithmetic is simply the binary equivalent of that earlier "ten's-complement" arithmetic.


用补法执行减法的优点是减少了硬件 的复杂性。不需要不同的数字电路来进行加减法运算 加法和减法只能由加法器执行。


尽管这个问题已经很老了,但还是让我说说我的看法。

在我解释这个之前,让我们回到基础上来。2'补码等于1'补码+ 1。 那么1的补是什么,它的加法意义是什么。

任何n位数和它的1的补数的和给出了可以用这n位表示的最大可能的数。 例子:

 0010 (2 in 4 bit system)
+1101 (1's complement of 2)
___________________________
 1111  (the highest number that we can represent by 4 bits)

现在如果我们尝试在结果中再加1会发生什么。这将导致溢出。

结果将是1 000,即0(因为我们处理的是4位数字,(左边的1是溢出)

So ,

Any n-bit number + its 1's complement = max n-bit number
Any n-bit number + its 1'complement + 1 = 0 ( as explained above, overflow will occur as we are adding 1 to max n-bit number)

于是有人决定把1的补体+ 1称为2'补体。所以上面的表述变成: 任何n位数+它的2的补= 0 也就是说2对一个数的补= -(该数的补)

所有这一切又产生了一个问题,为什么我们只能使用n位中的(n-1)来表示正数,为什么最左边的第n位表示符号(最左边的0表示+ve个数字,1表示-ve个数字)。例如,为什么我们在Java中只使用int的前31位来表示正数,如果第32位是1,它是-ve数。

 1100 (lets assume 12 in 4 bit system)
+0100(2's complement of 12)
___________________________

1 0000(结果为0,进位1溢出)

因此(n + 2'补n) = 0的方程组仍然成立。这里唯一的歧义是2对12的补码是0100,它也模糊地表示+8,而不是在2s补码系统中表示-12。

如果正数的最左边总是有一个0,这个问题就可以解决了。在这种情况下,它们的2的补位总是在最左边有一个1,我们就不会有相同的位集表示2的补位数和+ve数的模糊性。


在阅读这个问题的答案时,我看到了这条评论[编辑]。

0100(4)的补数是1100。现在1100是12如果我说正常的话。所以, 当我说标准1100时,它是12,但当我说2的补足1100时,那么 是-4?另外,在Java中,当1100(假设现在是4位)被存储时 如何确定它是+12还是-4 ?——hagrawal 7月2日16:53

在我看来,这条评论中提出的问题非常有趣,所以我想首先重新措辞,然后提供一个答案和一个例子。

系统如何确定一个或多个相邻字节必须如何解释?特别是,系统如何确定给定的字节序列是普通二进制数还是2的补数?

ANSWER -系统建立如何通过类型解释字节序列。 类型定义

需要考虑多少字节 如何解释这些字节

示例-下面我们假设

Char的长度为1字节 短的是2字节长 Int型和float型的长度是4字节

请注意,这些大小是特定于我的系统的。尽管非常常见,但它们可能因系统而异。如果您想知道它们在您的系统中是什么,请使用sizeof操作符。

首先,我们定义一个包含4个字节的数组,并将它们初始化为二进制数10111101,对应于十六进制数BD。

// BD(hexadecimal) = 10111101 (binary)
unsigned char   l_Just4Bytes[ 4 ]   =   { 0xBD, 0xBD, 0xBD, 0xBD };

然后我们使用不同的类型读取数组内容。

Unsigned char和signed char

// 10111101 as a PLAIN BINARY number equals 189
printf( "l_Just4Bytes as unsigned char  -> %hi\n", *( ( unsigned char* )l_Just4Bytes ) );

// 10111101 as a 2'S COMPLEMENT number equals -67
printf( "l_Just4Bytes as signed char    -> %i\n", *( ( signed char* )l_Just4Bytes ) );

未签名的空头和空头

// 1011110110111101 as a PLAIN BINARY number equals 48573
printf( "l_Just4Bytes as unsigned short -> %hu\n", *( ( unsigned short* )l_Just4Bytes ) );

// 1011110110111101 as a 2'S COMPLEMENT number equals -16963
printf( "l_Just4Bytes as short          -> %hi\n", *( ( short* )l_Just4Bytes ) );

Unsigned int, int和float

// 10111101101111011011110110111101 as a PLAIN BINARY number equals 3183328701
printf( "l_Just4Bytes as unsigned int   -> %u\n", *( ( unsigned int* )l_Just4Bytes ) );

// 10111101101111011011110110111101 as a 2'S COMPLEMENT number equals -1111638595
printf( "l_Just4Bytes as int            -> %i\n", *( ( int* )l_Just4Bytes ) );

// 10111101101111011011110110111101 as a IEEE 754 SINGLE-PRECISION number equals -0.092647
printf( "l_Just4Bytes as float          -> %f\n", *( ( float* )l_Just4Bytes ) );

内存中的4个字节(l_Just4Bytes[0..]3)始终保持完全相同。唯一改变的是我们如何理解它们。

同样,我们告诉系统如何通过类型来解释它们。

例如,上面我们使用了以下类型来解释l_Just4Bytes数组的内容

无符号字符:纯二进制的1字节 有符号字符:2的补码中的1字节 无符号短:2字节的纯二进制表示法 短:2字节的2补码 Unsigned int: 4字节的纯二进制表示法 Int: 4字节的2的补码 float:在IEEE 754单精度表示法中为4字节


[编辑]这篇文章是在user4581301的评论后编辑的。谢谢你抽出时间来写这些有用的句子!


A major advantage of two's-complement representation which hasn't yet been mentioned here is that the lower bits of a two's-complement sum, difference, or product are dependent only upon the corresponding bits of the operands. The reason that the 8 bit signed value for -1 is 11111111 is that subtracting any integer whose lowest 8 bits are 00000001 from any other integer whose lowest 8 bits are 0000000 will yield an integer whose lowest 8 bits are 11111111. Mathematically, the value -1 would be an infinite string of 1's, but all values within the range of a particular integer type will either be all 1's or all 0's past a certain point, so it's convenient for computers to "sign-extend" the most significant bit of a number as though it represented an infinite number of 1's or 0's.

Two's-complement is just about the only signed-number representation that works well when dealing with types larger than a binary machine's natural word size, since when performing addition or subtraction, code can fetch the lowest chunk of each operand, compute the lowest chunk of the result, and store that, then load the next chunk of each operand, compute the next chunk of the result, and store that, etc. Thus, even a processor which requires all additions and subtractions to go through a single 8-bit register can handle 32-bit signed numbers reasonably efficiently (slower than with a 32-bit register, of course, but still workable).

当使用C标准所允许的任何其他有符号表示时,结果的每一位都可能受到操作数的任何位的影响,这就需要将整个值一次保存在寄存器中,或者在计算之后进行额外的步骤,至少在某些情况下,需要读取、修改和重写结果的每个块。


为什么用Two2的补语系统来表示负数,而不是用One的补语系统,一个令人满意的答案是 二的补语系统解决了一的补语系统中存在的表示负数的0的多重表示和对进位的需要。

欲了解更多信息,请访问https://en.wikipedia.org/wiki/Signed_number_representations

用于末端绕行访问 https://en.wikipedia.org/wiki/End-around_carry


有不同类型的表示,它们是:

无符号数表示 有符号数字表示 补体表示 二补体表示法

无符号数字表示,仅用于表示正数

有符号的数字表示,用来表示正数和负数。在有符号数表示中,MSB位表示符号位,其余位表示数字。当MSB为0时表示数字为正,当MSB为1时表示数字为负。

有符号数表示的问题是0有两个值。

补码表示法的问题是0有两个值。

但如果我们使用2的补体表示,那么0就只有一个值,这就是为什么我们用2的补体形式表示负数。

来源:负数为什么以二进制补码形式存储


我有一个在某些情况下很重要的小补充:在这些限制条件下,2的恭维是唯一可能的表示:

无符号数和二的补数是恒等交换环。它们之间有一个同态。 它们共享相同的表示,对负数有不同的分支切割,(因此,为什么它们之间的加法和乘法是相同的)。 高位决定符号。

要知道为什么,它有助于降低基数;例如Z_4。

星座、星等和一个人的赞美都不能形成一个具有相同数量元素的环;一个症状是双零。因此,很难在边缘上工作;为了在数学上保持一致,它们需要检查溢出或陷阱表示。