我只是想知道为什么为了用二进制表示-1而使用二进制补码是有原因的:翻转位并加1?
-1 由 11111111 (二进制补码)表示,而不是(对我来说更直观) 10000001 ,它是二进制 1,第一位为负标志。
免责声明:我的工作不依赖二进制算术!
我只是想知道为什么为了用二进制表示-1而使用二进制补码是有原因的:翻转位并加1?
-1 由 11111111 (二进制补码)表示,而不是(对我来说更直观) 10000001 ,它是二进制 1,第一位为负标志。
免责声明:我的工作不依赖二进制算术!
这样做是为了让加法不需要任何特殊的逻辑来处理负数。查看维基百科上的文章。
假设你有两个数字,2 和 -1。在您表示数字的“直观”方式中,它们分别是0010
和1001
(我坚持使用 4 位的大小)。在两者的补码方式中,它们是0010
和1111
。现在,假设我想添加它们。
二进制补码加法非常简单。您通常添加数字,最后的任何进位位都会被丢弃。所以他们添加如下:
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 位。这是每个应用程序中存在的最基本和最常见的操作之一中的一个微小的额外步骤。
维基百科说明了一切:
二进制补码系统的优点是不需要加法和减法电路检查操作数的符号以确定是加还是减。此属性使系统更易于实现并且能够轻松处理更高精度的算术。此外,零只有一个表示,消除了与负零相关的微妙之处,后者存在于一个补码系统中。
换句话说,无论数字是否为负,加法都是一样的。
即使这个问题很老,让我投入我的 2 美分。
在我解释这个之前,让我们回到基础。2' 补码是 1's 补码 + 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'bit 数字 + 它的 2 的补码 = 0 这意味着一个数字的 2 的补码 = - (那个数字的)
所有这些又产生了一个问题,为什么我们只能使用 n 位中的 (n-1) 来表示正数以及为什么最左边的第 n 位表示符号(最左边的位上的 0 表示 +ve 数,而 1 表示-ve 数字)。例如,如果第 32 位是 1,那么为什么我们只使用 java 中 int 的前 31 位来表示正数,它是一个 -ve 数。
1100 (lets assume 12 in 4 bit system)
+0100(2's complement of 12)
___________________________
1 0000(结果为零,进位 1 溢出)
因此 (n + 2'complement of n) = 0 的系统仍然有效。这里唯一的歧义是 12 的 2 的补码是 0100 ,它也模棱两可地表示 +8 ,而不是在 2s 补码系统中表示 -12 。
如果正数的最左边总是有一个 0,这个问题就会得到解决。在这种情况下,它们的 2 的补码在最左边的位中总是有一个 1,并且我们不会有表示 2 的补码数和 +ve 数的同一组位的歧义。
二进制补码允许以正常方式进行加法和减法(就像你为无符号数字缠绕一样)。它还可以防止 -0 (一种表示 0 的单独方法,它使用普通的逐位比较数字方法不等于 0)。
这是为了简化数字的和和差。在 2 的补码中编码的负数和正数之和与以正常方式将它们相加相同。
二进制补码允许在没有任何特殊逻辑的情况下将负数和正数相加。
如果您尝试使用您的方法
10000001 (-1)
+00000001 (1)添加 1 和 -1,您
将得到
10000010 (-2)
相反,通过使用二进制补码,我们可以添加
11111111 (-1)
+00000001 (1) 你得到
00000000 (0)
减法也是如此。
此外,如果您尝试从 6(两个正数)中减去 4,您可以使用 2 的补码 4 并将两者相加 6 + (-4) = 6 - 4 = 2
这意味着正数和负数的减法和加法都可以由 cpu 中的同一电路完成。
该操作的通常实现是“翻转位并加 1”,但还有另一种定义它的方法可能会使基本原理更清楚。如果您采用通常的无符号表示形式,2 的补码是您得到的形式,其中每个位控制 2 的下一个幂,并且只使最重要的项为负数。
取一个 8 位值 a 7 a 6 a 5 a 4 a 3 a 2 a 1 a 0
通常的无符号二进制解释是:
2 7 *a 7 + 2 6 *a 6 + 2 5 *a 5 + 2 4 *a 4 + 2 3 *a 3 + 2 2 *a 2 + 2 1 *a 1 + 2 0 *a 0
11111111 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255
二进制补码解释为:
-2 7 *a 7 + 2 6 *a 6 + 2 5 *a 5 + 2 4 *a 4 + 2 3 *a 3 + 2 2 *a 2 + 2 1 *a 1 + 2 0 *a 0
11111111 = -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = -1
其他位根本没有改变含义,并且进入7是“溢出”并且不会起作用,因此几乎所有算术运算都无需修改即可工作(正如其他人所指出的那样)。符号幅度通常检查符号位并使用不同的逻辑。
要扩展其他答案:
在二进制补码中
除法确实需要不同的机制。
所有这些都是正确的,因为二进制补码只是普通的模运算,我们选择通过减去模数将某些数字视为负数。
阅读这个问题的答案,我发现了这个评论[编辑]。
2 的 0100(4) 的补码将是 1100。如果我正常地说,现在 1100 是 12。所以,当我说正常的 1100 时它是 12,但是当我说 2 的补码 1100 时它是 -4?另外,在Java中,当存储1100(现在假设4位)时,如何确定它是+12还是-4?– 哈格拉瓦尔 7 月 2 日 16:53
在我看来,这个评论中提出的问题非常有趣,所以我想首先重新表述它,然后提供一个答案和一个例子。
问题 – 系统如何确定如何解释一个或多个相邻字节?特别是,系统如何确定给定的字节序列是普通二进制数还是 2 的补码?
ANSWER – 系统确定如何通过类型解释字节序列。类型定义
示例 – 下面我们假设
char
的长度为 1 个字节short
的长度为 2 个字节int
's 和float
's 是 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 ) );
unsigned short
和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
和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 ( l_Just4Bytes[ 0..3 ]
) 中的 4 个字节始终保持完全相同。唯一改变的是我们如何解释它们。
同样,我们通过types告诉系统如何解释它们。
例如,上面我们使用了以下类型来解释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 评论后编辑。感谢您抽出宝贵的时间放弃这几条有用的线路!
值得注意的是,在一些早期的加法机器上,在数字计算机时代之前,减法是通过让操作员在每个键上使用一组不同颜色的图例输入值来执行的(因此每个键将输入 9 减去要计算的数字)减去),然后按下特殊按钮将假定进位进入计算。因此,在六位数机器上,要从一个值中减去 1234,操作员将按下通常指示“998,765”的键并按下按钮将该值加一加到正在进行的计算中。二进制补码算术只是早期“十补码”算术的二进制等价物。
用补法进行减法的优点是降低了硬件
复杂度。加减法不需要不同的数字电路。加法和减法都只用加法器来完成。
使用二进制补码是因为它在电路中实现起来更简单,并且也不允许负零。
如果有 x 位,二进制补码的范围为 +(2^x/2+1) 到 -(2^x/2)。一个的补码将从 +(2^x/2) 到 -(2^x/2),但允许负零(在 4 位 1 的补码系统中,0000 等于 1000)。
好吧,您的意图并不是真正反转二进制数的所有位。它实际上是从 1 中减去它的每个数字。幸运的巧合是,从 1 中减去 1 得到 0,从 1 中减去 0 得到 1。所以翻转位有效地执行了这种减法。
但是为什么你会发现每个数字与 1 的差异?好吧,你不是。您的实际意图是计算给定二进制数与另一个具有相同位数但仅包含 1 的二进制数的差异。例如,如果您的数字是 10110001,当您翻转所有这些位时,您实际上是在计算 (11111111 - 10110001)。
这解释了计算二进制补码的第一步。现在让我们在图片中包括第二步——加 1。
对上述二元方程加 1:
11111111 - 10110001 + 1
你得到了什么?这个:
100000000 - 10110001
这是最终的等式。通过执行这两个步骤,您试图找到最终的区别:从另一个二进制数中减去二进制数,并带有一个额外的数字,并且除了在最重要的位位置之外包含零。
但是为什么我们真的渴望这种差异呢?好吧,从这里开始,我想如果你阅读维基百科的文章会更好。
我们只对加法和减法执行加法运算。我们将第二个操作数添加到第一个操作数以进行加法。对于减法,我们将第二个操作数的 2 的补码添加到第一个操作数。
使用 2 的补码表示,我们不需要单独的数字分量来进行减法——只使用加法器和补码器。
这里尚未提到的二进制补码表示的一个主要优点是二进制补码和、差或乘积的低位仅取决于操作数的相应位。-1 的 8 位有符号值的原因是,从任何其他最低 8 位为的整数11111111
中减去最低 8 位为的任何整数将产生一个最低 8 位为00000001
0000000
11111111
. 从数学上讲,值 -1 将是一个无限的 1 字符串,但在特定整数类型范围内的所有值在某个点之后要么全为 1,要么全为 0,因此计算机可以方便地“符号扩展”一个数字的最高有效位,就好像它代表无限数量的 1 或 0。
在处理大于二进制机器的自然字长的类型时,二进制补码几乎是唯一一种工作良好的有符号数表示,因为在执行加法或减法时,代码可以获取每个操作数的最低块,计算结果,并存储它,然后加载每个操作数的下一个块,计算结果的下一个块,并存储它,等等。因此,即使是一个需要所有加法和减法的处理器都通过一个 8 位寄存器可以相当有效地处理 32 位有符号数(当然,比使用 32 位寄存器要慢,但仍然可行)。
当使用 C 标准允许的任何其他有符号表示时,结果的每一位都可能受到操作数的任何位的影响,因此有必要一次将整个值保存在寄存器中,或者在计算之后使用额外的至少在某些情况下,需要读取、修改和重写每个结果块的步骤。
有不同类型的表示,它们是:
- 无符号数表示,用于仅表示正数
- 有符号数表示用于表示正数和负数。在有符号数表示中,MSB 位表示符号位,其余位表示数字。当 MSB 为 0 时表示数字为正,当 MSB 为 1 时表示数字为负。
有符号数表示的问题是 0 有两个值。
一个补码表示的问题是 0 有两个值。
但是如果我们使用二进制补码表示,那么 0 将只有一个值,这就是我们以二进制补码形式表示负数的原因。
为什么用 Two2's Complement 来表示负数而不是 One's Complement 系统的一个令人满意的答案是,Two's Complement 系统解决了0 的多重表示问题以及 One's Complement 系统中存在的表示负数的end-around-carry的需要。数字。
欲了解更多信息,请访问https://en.wikipedia.org/wiki/Signed_number_representations
对于 End-around-carry 访问 https://en.wikipedia.org/wiki/End-around_carry