为什么对签名数字更喜欢两个补码而不是符号和数量?

问题描述 投票:183回答:18

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

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

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

binary math twos-complement negative-number internal-representation
18个回答
309
投票

这样做是为了使加法不需要任何特殊的逻辑来处理负数。看看the article on Wikipedia

假设你有两个数字,2和-1。用你的“直观”表示数字的方式,它们分别是00101001(我坚持4位大小)。在两者的补充方式中,他们是00101111。现在,让我们说我想添加它们。

两个补码的添加非常简单。您可以正常添加数字,最后的任何进位都将被丢弃。所以他们添加如下:

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

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

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

  0010
+ 1001
= 1011

哪个是-3,对吗?在这种情况下,简单的添加不起作用。您需要注意其中一个数字是否定的,如果是这种情况则使用不同的算法。

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

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

0000  "zero"
1000  "negative zero"

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

以这种方式存储整数还有另外一个好处,那就是当你需要扩展寄存器的宽度时,存储的值。使用二进制补码,将一个4位数存储在一个8位寄存器中是一个重复的问题。最重要的一点:

    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位。这是每个应用程序中最基本和最常见操作之一的一小步。


1
投票

您可以在斯坦福大学的YouTube频道观看的一系列名为“编程范式”的讲座中,观看斯坦福大学的Jerry Cain教授在第二讲(关于2的补充开始于13:00左右的解释)中解释两者的补充。这是讲座系列的链接:http://www.youtube.com/view_play_list?p=9D558D49CA734A02


0
投票

使用二进制补码是因为它在电路中实现起来更简单,并且也不允许负零。

如果有x位,则二进制补码的范围为+(2 ^ x / 2 + 1)至 - (2 ^ x / 2)。一个补码将从+(2 ^ x / 2)运行到 - (2 ^ x / 2),但允许负零(0000在4位1的补码系统中等于1000)。


0
投票

那么,你的意图并不是要反转二进制数的所有位。它实际上是从1中减去每个数字。这只是一个幸运的巧合,从1减去1得到0并从1减去0导致1.所以翻转位实际上是执行这个减法。

但为什么你发现每个数字与1的差异?好吧,你不是。您的实际意图是计算给定二进制数与另一个二进制数的差值,该二进制数具有相同的位数但仅包含1。例如,如果您的数字是10110001,当您翻转所有这些位时,您就会有效地计算(11111111 - 10110001)。

这解释了计算Two's补语的第一步。现在让我们在图片中包括第二步 - 添加1 - 。

在上面的二元方程中加1:

11111111 - 10110001 + 1

你得到了什么?这个:

100000000 - 10110001

这是最后的等式。通过执行这两个步骤,你试图找到这个,最后的差异:二进制数从另一个二进制数中减去一个额外的数字并包含零,除了在最具意义的位位置。

但是为什么我们真的在这个差异之后呢?好吧,从这里开始,我想如果你阅读Wikipedia article会更好。


0
投票

我们只对加法和减法执行加法运算。我们将第二个操作数添加到第一个操作数中以进行添加。对于减法,我们将第二个操作数的2的补码添加到第一个操作数。

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


0
投票

值得注意的是,在一些早期的添加机器上,在数字计算机时代之前,通过让操作员在每个键上使用不同颜色的图例集输入值来执行减法(因此每个键将输入9减去要编号的数字)减去),然后按一个特殊按钮就会假定进行计算。因此,在一个六位数的机器上,要从一个值中减去1234,操作员将按下通常指示“998,765”的按键,然后按一个按钮将该值加1加到正在进行的计算中。二进制补码算术只是早期“十次补码”算术的二进制等价。


0
投票

通过补码方法执行减法的优点是硬件减少 复杂性。不需要用于加法和减法的不同数字电路。加法和减法仅由加法器执行。


0
投票

这里尚未提到的二进制补码表示的主要优点是二进制补码和,差或乘积的低位仅取决于操作数的相应位。 -1的8位有符号值是11111111的原因是从最低8位为00000001的任何其他整数中减去最低8位为0000000的任何整数将产生最低8位为11111111的整数。在数学上,值-1将是1的无限字符串,但是特定整数类型范围内的所有值将全部为1或全部0超过某个点,因此计算机可以“签名扩展”数字的最高有效位,好像它代表无穷多的1或0。

二进制补码只是在处理大于二进制机器的自然字大小的类型时唯一有效的有符号数表示,因为当执行加法或减法时,代码可以获取每个操作数的最低块,计算最低的块。结果,然后存储,然后加载每个操作数的下一个块,计算结果的下一个块,然后存储,等等。因此,即使是需要所有加法和减法的处理器都要经过一个8位寄存器可以合理有效地处理32位有符号数(当然,比32位寄存器慢,但仍然可行)。

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


-1
投票

为什么Two2的补语用于表示负数而不是One的补语系统的一个令人满意的答案是,Two的补语系统解决了0的多重表示问题以及在表示负数的一元补语系统中存在的结束运算的需要数字。

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

为了结束携带访问https://en.wikipedia.org/wiki/End-around_carry


-1
投票

因为CPU厂商很懒!


18
投票

Wikipedia说的一切:

二进制补码系统的优点是不需要加法和减法电路检查操作数的符号以确定是加或减。该属性使系统更易于实现,并且能够轻松处理更高精度的算术。此外,零只有一个表示,避免了与负零相关的细微差别,它存在于补充系统中。

换句话说,无论数字是否为负数,添加都是相同的。


12
投票

即使这个问题已经过时了,让我加入我的2美分。

在我解释之前,让我们回到基础。 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 0000为0(因为我们使用4位数,(左边的1是溢出)

所以,

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位代表符号(最左边的位表示+ 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(结果为零,进位1溢出)

因此,(n + 2'实现n)= 0的系统仍然有效。这里唯一不明确的是12的补码12是0100,它除了在2s补码系统中代表-12之外,它也模糊地表示+8。

如果正数在最左边的位中始终为0,则会解决此问题。在那种情况下,它们的2的补码在它们最左边的位中总是有1,并且我们不会有同一组位的模糊性,表示2的补码数和+ ve数。


8
投票

Two's complement允许以正常方式完成加法和减法(就像你为无符号数字缠绕一样)。它还可以防止-0(使用正常的逐位比较数字方法表示0不会等于0的单独方式)。


6
投票

这是为了简化数字的总和和差异。在2的补码中编码的负数和正数之和与以正常方式对它们求和相同。


5
投票

操作的通常实现是“翻转位并添加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 11111111 = 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 11111111 = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1

其他任何一个位都没有改变含义,并且进入a7是“溢出”并且预期不起作用,因此几乎所有的算术运算都没有修改(正如其他人所指出的那样)。符号幅度通常检查符号位并使用不同的逻辑。


4
投票

扩展其他答案:

在二的补充

  • 添加与普通正整数添加的机制相同。
  • 减法也不会改变
  • 乘法也是!

分部确实需要不同的机制。

所有这些都是正确的,因为两个补码只是正常的模运算,我们选择通过减去模数来看一些数字为负数。


3
投票

二进制补码允许将负数和正数加在一起,而无需任何特殊逻辑。

如果您尝试使用您的方法添加1和-1 10000001(-1) +00000001(1) 你得到 10000010(-2)

相反,通过使用两个补码,我们可以添加

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

减法也是如此。

此外,如果你试图从6减去4(两个正数)你可以2补全4并将两个加在一起6 +( - 4)= 6 - 4 = 2

这意味着减去和添加正数和负数都可以通过cpu中的相同电路完成。


2
投票

阅读这个问题的答案,我发现了这个评论[编辑]。

2的补充0100(4)将是1100.如果我正常说,现在1100是12。那么,当我说正常1100然后它是12,但是当我说2的补数1100然后它是-4?此外,在Java中存储1100(现在假设为4位),然后如何确定它是+12还是-4? - hagrawal 7月2日16:53

在我看来,这个评论中提到的问题非常有趣,所以我首先想要改写它,然后提供一个答案和一个例子。

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

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

  • 必须考虑多少字节
  • 如何解释这些字节

示例 - 下面我们假设

  • char的长度为1个字节
  • short的长度为2个字节
  • intfloat的长度为4个字节

请注意,这些尺寸特定于我的系统。虽然很常见,但它们可能因系统而异。如果您对系统中的内容感到好奇,请使用sizeof operator

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

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

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

unsigned char and 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 ) );

unsigned short and short

// 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 and 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 ) );

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

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

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

  • unsigned char:普通二进制1字节
  • signed char:2的补码中的1个字节
  • unsigned short:普通二进制表示法中的2个字节
  • short:2的补码中有2个字节
  • unsigned int:普通二进制表示法中的4个字节
  • int:2的补码中有4个字节
  • float:IEEE 754单精度表示法中的4个字节

[编辑]此帖子已在用户4581301发表评论后进行了编辑。感谢您抽出宝贵时间放弃这几条有用的线路!

© www.soinside.com 2019 - 2024. All rights reserved.