我只是好奇为什么要使用二进制的补码来表示-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"
这两个数字在直观上看起来相同,但存储时具有不同的值。每个应用程序都需要采取额外的步骤来确保非零值也不是负零。
以这种方式存储整数还有另一个好处,即当您需要扩展存储值的寄存器的宽度时。使用二进制补码,在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位。这是在每个应用程序中都存在的最基本和常见的操作之一中的一个微小额外步骤。
我们是如何得出二进制补码的。
http://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html - Ankit维基百科 给出了以下说明:
二进制补码系统的优势在于不需要加法和减法电路检查操作数的符号来判断是加法还是减法。这种特性使得系统实现更加简单,能够轻松处理高精度算术。此外,零只有唯一一种表示方式,消除了出现在补码反码系统中的负零的微妙之处。
换句话说,无论数字是否为负数,加法运算都是相同的。
即使这个问题早已过时,让我也来发表一下我的看法。
在我解释这个问题之前,让我们回到基础知识。二进制补码是一的补码加1得到的。 那么什么是一的补码,以及它在加法中的意义是什么。
任何n位数和它的一补数之和都会得到可以由这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位表示符号(左侧最高位为0表示正数,1表示负数)。例如,在Java中,我们为什么要使用int的前31位来表示正数,如果第32位是1,则表示这是一个负数。
1100 (lets assume 12 in 4 bit system)
+0100(2's complement of 12)
___________________________
1 0000 (结果为零,进位1溢出)
因此,(n + 2的补码)= 0的系统仍然有效。这里唯一的模棱两可之处是12的2的补码是0100,除了在2的补码系统中表示-12之外,还模棱两可地表示+8。
如果正数总是在其最左边的位上有一个0,则此问题将得到解决。在这种情况下,它们的2的补码将始终在最左边的位上具有1,并且我们不会遇到相同位集表示2的补码数字以及+ve数字的歧义。
二进制补码允许负数和正数相加而不需要任何特殊逻辑。
如果您尝试使用自己的方法将1和-1相加
10000001 (-1)
+00000001 (1)
你会得到
10000010 (-2)
相反,通过使用二进制补码,我们可以相加
11111111 (-1)
+00000001 (1)
你会得到
00000000 (0)
减法也是这样的。
此外,如果您尝试从6中减去4(两个正数),则可以使用二进制补码对4进行操作,并将它们相加6 +(-4)= 6-4 = 2
这意味着CPU中的同一电路可以处理正数和负数的加减法。
这是为了简化数字的加减而设计的。在2的补码中,一个负数和一个正数相加的结果与以常规方式相加的结果相同。
进一步解释其他人的答案:
在二进制补码中
除法需要使用不同的机制。
所有这些都是正确的,因为二进制补码只是正常的模算术,在其中我们选择通过减去模数将某些数字看作负数。
unsigned mul(unsigned short x, unsigned short y) { return x*y; }
[16位短整型;32位整型]的代码时,如果乘积大于2147483647,则会偶尔生成会出现故障的代码。 - supercatchar
的长度为1个字节short
的长度为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 ) );
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 ) );
l_Just4Bytes [0..3]
)始终保持完全相同。唯一变化的是我们如何解释它们。l_Just4Bytes
数组的内容:
unsigned char
:1个字节的二进制数signed char
:1个字节的2补码unsigned short
:2个字节的纯二进制表示法short
:2个字节的2补码unsigned int
:4个字节的纯二进制表示法int
:4个字节的2补码float
:4个字节的IEEE 754单精度表示法int x = -4
,然后执行 printf("%d", x)
,那它会被如何解释呢?另外,unsigned int
和 signed int
以及 %d
和 %u
有什么区别……这个问题困扰我已经很长时间了。谢谢。 - Suraj Jainprintf
格式,您可以在 int
或 unsigned int
上使用 %d
和 %u
。实际上,如果您使用 %d
,“整数参数将以带有符号的十进制形式[-]dddd转换”,并且使用 %u
发生类似的转换。在C标准中,d
和u
被称为“转换说明符”。 - mw215使用二进制补码是因为它在电路实现上更简单,而且不允许出现负零。
如果有x位,则二进制补码的范围为+(2^x/2+1)到-(2^x/2)。一进制补码将从+(2^x/2)到-(2^x/2)运行,但将允许出现负零(在4位1进制补码系统中,0000等于1000)。